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