upgraded to xpdf-3.01pl1
[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 #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       goto err1;
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       goto err1;
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       goto err1;
320     }
321     left = obj2.getNum();
322     obj2.free();
323     if (!a->get(3, &obj2)->isNum()) {
324       error(-1, "Bad annotation destination position");
325       goto err1;
326     }
327     bottom = obj2.getNum();
328     obj2.free();
329     if (!a->get(4, &obj2)->isNum()) {
330       error(-1, "Bad annotation destination position");
331       goto err1;
332     }
333     right = obj2.getNum();
334     obj2.free();
335     if (!a->get(5, &obj2)->isNum()) {
336       error(-1, "Bad annotation destination position");
337       goto err1;
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       goto err1;
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       goto err1;
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
434   // error
435   } else {
436     error(-1, "Illegal annotation destination");
437   }
438 }
439
440 LinkGoTo::~LinkGoTo() {
441   if (dest)
442     delete dest;
443   if (namedDest)
444     delete namedDest;
445 }
446
447 //------------------------------------------------------------------------
448 // LinkGoToR
449 //------------------------------------------------------------------------
450
451 LinkGoToR::LinkGoToR(Object *fileSpecObj, Object *destObj) {
452   dest = NULL;
453   namedDest = NULL;
454
455   // get file name
456   fileName = getFileSpecName(fileSpecObj);
457
458   // named destination
459   if (destObj->isName()) {
460     namedDest = new GString(destObj->getName());
461   } else if (destObj->isString()) {
462     namedDest = destObj->getString()->copy();
463
464   // destination dictionary
465   } else if (destObj->isArray()) {
466     dest = new LinkDest(destObj->getArray());
467     if (!dest->isOk()) {
468       delete dest;
469       dest = NULL;
470     }
471
472   // error
473   } else {
474     error(-1, "Illegal annotation destination");
475   }
476 }
477
478 LinkGoToR::~LinkGoToR() {
479   if (fileName)
480     delete fileName;
481   if (dest)
482     delete dest;
483   if (namedDest)
484     delete namedDest;
485 }
486
487
488 //------------------------------------------------------------------------
489 // LinkLaunch
490 //------------------------------------------------------------------------
491
492 LinkLaunch::LinkLaunch(Object *actionObj) {
493   Object obj1, obj2;
494
495   fileName = NULL;
496   params = NULL;
497
498   if (actionObj->isDict()) {
499     if (!actionObj->dictLookup("F", &obj1)->isNull()) {
500       fileName = getFileSpecName(&obj1);
501     } else {
502       obj1.free();
503 #ifdef WIN32
504       if (actionObj->dictLookup("Win", &obj1)->isDict()) {
505         obj1.dictLookup("F", &obj2);
506         fileName = getFileSpecName(&obj2);
507         obj2.free();
508         if (obj1.dictLookup("P", &obj2)->isString()) {
509           params = obj2.getString()->copy();
510         }
511         obj2.free();
512       } else {
513         error(-1, "Bad launch-type link action");
514       }
515 #else
516       //~ This hasn't been defined by Adobe yet, so assume it looks
517       //~ just like the Win dictionary until they say otherwise.
518       if (actionObj->dictLookup("Unix", &obj1)->isDict()) {
519         obj1.dictLookup("F", &obj2);
520         fileName = getFileSpecName(&obj2);
521         obj2.free();
522         if (obj1.dictLookup("P", &obj2)->isString()) {
523           params = obj2.getString()->copy();
524         }
525         obj2.free();
526       } else {
527         error(-1, "Bad launch-type link action");
528       }
529 #endif
530     }
531     obj1.free();
532   }
533 }
534
535 LinkLaunch::~LinkLaunch() {
536   if (fileName)
537     delete fileName;
538   if (params)
539     delete params;
540 }
541
542 //------------------------------------------------------------------------
543 // LinkURI
544 //------------------------------------------------------------------------
545
546 LinkURI::LinkURI(Object *uriObj, GString *baseURI) {
547   GString *uri2;
548   int n;
549   char c;
550
551   uri = NULL;
552   if (uriObj->isString()) {
553     uri2 = uriObj->getString()->copy();
554     if (baseURI && baseURI->getLength() > 0) {
555       n = strcspn(uri2->getCString(), "/:");
556       if (n == uri2->getLength() || uri2->getChar(n) == '/') {
557         uri = baseURI->copy();
558         c = uri->getChar(uri->getLength() - 1);
559         if (c == '/' || c == '?') {
560           if (uri2->getChar(0) == '/') {
561             uri2->del(0);
562           }
563         } else {
564           if (uri2->getChar(0) != '/') {
565             uri->append('/');
566           }
567         }
568         uri->append(uri2);
569         delete uri2;
570       } else {
571         uri = uri2;
572       }
573     } else {
574       uri = uri2;
575     }
576   } else {
577     error(-1, "Illegal URI-type link");
578   }
579 }
580
581 LinkURI::~LinkURI() {
582   if (uri)
583     delete uri;
584 }
585
586 //------------------------------------------------------------------------
587 // LinkNamed
588 //------------------------------------------------------------------------
589
590 LinkNamed::LinkNamed(Object *nameObj) {
591   name = NULL;
592   if (nameObj->isName()) {
593     name = new GString(nameObj->getName());
594   }
595 }
596
597 LinkNamed::~LinkNamed() {
598   if (name) {
599     delete name;
600   }
601 }
602
603 //------------------------------------------------------------------------
604 // LinkMovie
605 //------------------------------------------------------------------------
606
607 LinkMovie::LinkMovie(Object *annotObj, Object *titleObj) {
608   annotRef.num = -1;
609   title = NULL;
610   if (annotObj->isRef()) {
611     annotRef = annotObj->getRef();
612   } else if (titleObj->isString()) {
613     title = titleObj->getString()->copy();
614   } else {
615     error(-1, "Movie action is missing both the Annot and T keys");
616   }
617 }
618
619 LinkMovie::~LinkMovie() {
620   if (title) {
621     delete title;
622   }
623 }
624
625 //------------------------------------------------------------------------
626 // LinkUnknown
627 //------------------------------------------------------------------------
628
629 LinkUnknown::LinkUnknown(char *actionA) {
630   action = new GString(actionA);
631 }
632
633 LinkUnknown::~LinkUnknown() {
634   delete action;
635 }
636
637 //------------------------------------------------------------------------
638 // LinkBorderStyle
639 //------------------------------------------------------------------------
640
641 LinkBorderStyle::LinkBorderStyle(LinkBorderType typeA, double widthA,
642                                  double *dashA, int dashLengthA,
643                                  double rA, double gA, double bA) {
644   type = typeA;
645   width = widthA;
646   dash = dashA;
647   dashLength = dashLengthA;
648   r = rA;
649   g = gA;
650   b = bA;
651 }
652
653 LinkBorderStyle::~LinkBorderStyle() {
654   if (dash) {
655     gfree(dash);
656   }
657 }
658
659 //------------------------------------------------------------------------
660 // Link
661 //------------------------------------------------------------------------
662
663 Link::Link(Dict *dict, GString *baseURI) {
664   Object obj1, obj2, obj3;
665   LinkBorderType borderType;
666   double borderWidth;
667   double *borderDash;
668   int borderDashLength;
669   double borderR, borderG, borderB;
670   double t;
671   int i;
672
673   borderStyle = NULL;
674   action = NULL;
675   ok = gFalse;
676
677   // get rectangle
678   if (!dict->lookup("Rect", &obj1)->isArray()) {
679     error(-1, "Annotation rectangle is wrong type");
680     goto err2;
681   }
682   if (!obj1.arrayGet(0, &obj2)->isNum()) {
683     error(-1, "Bad annotation rectangle");
684     goto err1;
685   }
686   x1 = obj2.getNum();
687   obj2.free();
688   if (!obj1.arrayGet(1, &obj2)->isNum()) {
689     error(-1, "Bad annotation rectangle");
690     goto err1;
691   }
692   y1 = obj2.getNum();
693   obj2.free();
694   if (!obj1.arrayGet(2, &obj2)->isNum()) {
695     error(-1, "Bad annotation rectangle");
696     goto err1;
697   }
698   x2 = obj2.getNum();
699   obj2.free();
700   if (!obj1.arrayGet(3, &obj2)->isNum()) {
701     error(-1, "Bad annotation rectangle");
702     goto err1;
703   }
704   y2 = obj2.getNum();
705   obj2.free();
706   obj1.free();
707   if (x1 > x2) {
708     t = x1;
709     x1 = x2;
710     x2 = t;
711   }
712   if (y1 > y2) {
713     t = y1;
714     y1 = y2;
715     y2 = t;
716   }
717
718   // get the border style info
719   borderType = linkBorderSolid;
720   borderWidth = 1;
721   borderDash = NULL;
722   borderDashLength = 0;
723   borderR = 0;
724   borderG = 0;
725   borderB = 1;
726   if (dict->lookup("BS", &obj1)->isDict()) {
727     if (obj1.dictLookup("S", &obj2)->isName()) {
728       if (obj2.isName("S")) {
729         borderType = linkBorderSolid;
730       } else if (obj2.isName("D")) {
731         borderType = linkBorderDashed;
732       } else if (obj2.isName("B")) {
733         borderType = linkBorderEmbossed;
734       } else if (obj2.isName("I")) {
735         borderType = linkBorderEngraved;
736       } else if (obj2.isName("U")) {
737         borderType = linkBorderUnderlined;
738       }
739     }
740     obj2.free();
741     if (obj1.dictLookup("W", &obj2)->isNum()) {
742       borderWidth = obj2.getNum();
743     }
744     obj2.free();
745     if (obj1.dictLookup("D", &obj2)->isArray()) {
746       borderDashLength = obj2.arrayGetLength();
747       borderDash = (double *)gmallocn(borderDashLength, sizeof(double));
748       for (i = 0; i < borderDashLength; ++i) {
749         if (obj2.arrayGet(i, &obj3)->isNum()) {
750           borderDash[i] = obj3.getNum();
751         } else {
752           borderDash[i] = 1;
753         }
754         obj3.free();
755       }
756     }
757     obj2.free();
758   } else {
759     obj1.free();
760     if (dict->lookup("Border", &obj1)->isArray()) {
761       if (obj1.arrayGetLength() >= 3) {
762         if (obj1.arrayGet(2, &obj2)->isNum()) {
763           borderWidth = obj2.getNum();
764         }
765         obj2.free();
766         if (obj1.arrayGetLength() >= 4) {
767           if (obj1.arrayGet(3, &obj2)->isArray()) {
768             borderType = linkBorderDashed;
769             borderDashLength = obj2.arrayGetLength();
770             borderDash = (double *)gmallocn(borderDashLength, sizeof(double));
771             for (i = 0; i < borderDashLength; ++i) {
772               if (obj2.arrayGet(i, &obj3)->isNum()) {
773                 borderDash[i] = obj3.getNum();
774               } else {
775                 borderDash[i] = 1;
776               }
777               obj3.free();
778             }
779           } else {
780             // Adobe draws no border at all if the last element is of
781             // the wrong type.
782             borderWidth = 0;
783           }
784           obj2.free();
785         }
786       }
787     }
788   }
789   obj1.free();
790   if (dict->lookup("C", &obj1)->isArray() && obj1.arrayGetLength() == 3) {
791     if (obj1.arrayGet(0, &obj2)->isNum()) {
792       borderR = obj2.getNum();
793     }
794     obj1.free();
795     if (obj1.arrayGet(1, &obj2)->isNum()) {
796       borderG = obj2.getNum();
797     }
798     obj1.free();
799     if (obj1.arrayGet(2, &obj2)->isNum()) {
800       borderB = obj2.getNum();
801     }
802     obj1.free();
803   }
804   obj1.free();
805   borderStyle = new LinkBorderStyle(borderType, borderWidth,
806                                     borderDash, borderDashLength,
807                                     borderR, borderG, borderB);
808
809   // look for destination
810   if (!dict->lookup("Dest", &obj1)->isNull()) {
811     action = LinkAction::parseDest(&obj1);
812
813   // look for action
814   } else {
815     obj1.free();
816     if (dict->lookup("A", &obj1)->isDict()) {
817       action = LinkAction::parseAction(&obj1, baseURI);
818     }
819   }
820   obj1.free();
821
822   // check for bad action
823   if (action) {
824     ok = gTrue;
825   }
826
827   return;
828
829  err1:
830   obj2.free();
831  err2:
832   obj1.free();
833 }
834
835 Link::~Link() {
836   if (borderStyle) {
837     delete borderStyle;
838   }
839   if (action) {
840     delete action;
841   }
842 }
843
844 //------------------------------------------------------------------------
845 // Links
846 //------------------------------------------------------------------------
847
848 Links::Links(Object *annots, GString *baseURI) {
849   Link *link;
850   Object obj1, obj2;
851   int size;
852   int i;
853
854   links = NULL;
855   size = 0;
856   numLinks = 0;
857
858   if (annots->isArray()) {
859     for (i = 0; i < annots->arrayGetLength(); ++i) {
860       if (annots->arrayGet(i, &obj1)->isDict()) {
861         if (obj1.dictLookup("Subtype", &obj2)->isName("Link")) {
862           link = new Link(obj1.getDict(), baseURI);
863           if (link->isOk()) {
864             if (numLinks >= size) {
865               size += 16;
866               links = (Link **)greallocn(links, size, sizeof(Link *));
867             }
868             links[numLinks++] = link;
869           } else {
870             delete link;
871           }
872         }
873         obj2.free();
874       }
875       obj1.free();
876     }
877   }
878 }
879
880 Links::~Links() {
881   int i;
882
883   for (i = 0; i < numLinks; ++i)
884     delete links[i];
885   gfree(links);
886 }
887
888 LinkAction *Links::find(double x, double y) {
889   int i;
890
891   for (i = numLinks - 1; i >= 0; --i) {
892     if (links[i]->inRect(x, y)) {
893       return links[i]->getAction();
894     }
895   }
896   return NULL;
897 }
898
899 GBool Links::onLink(double x, double y) {
900   int i;
901
902   for (i = 0; i < numLinks; ++i) {
903     if (links[i]->inRect(x, y))
904       return gTrue;
905   }
906   return gFalse;
907 }