Initial revision
[swftools.git] / pdf2swf / xpdf / Link.cc
1 //========================================================================
2 //
3 // Link.cc
4 //
5 // Copyright 1996 Derek B. Noonburg
6 //
7 //========================================================================
8
9 #ifdef __GNUC__
10 #pragma implementation
11 #endif
12
13 #include <stddef.h>
14 #include <string.h>
15 #include "gmem.h"
16 #include "GString.h"
17 #include "Error.h"
18 #include "Object.h"
19 #include "Array.h"
20 #include "Dict.h"
21 #include "Link.h"
22
23 //------------------------------------------------------------------------
24
25 static GString *getFileSpecName(Object *fileSpecObj);
26
27 //------------------------------------------------------------------------
28 // LinkDest
29 //------------------------------------------------------------------------
30
31 LinkDest::LinkDest(Array *a, GBool pageIsRef1) {
32   Object obj1, obj2;
33
34   // initialize fields
35   pageIsRef = pageIsRef1;
36   left = bottom = right = top = zoom = 0;
37   ok = gFalse;
38
39   // get page
40   if (pageIsRef) {
41     if (!a->getNF(0, &obj1)->isRef()) {
42       error(-1, "Bad annotation destination");
43       goto err2;
44     }
45     pageRef.num = obj1.getRefNum();
46     pageRef.gen = obj1.getRefGen();
47     obj1.free();
48   } else {
49     if (!a->get(0, &obj1)->isInt()) {
50       error(-1, "Bad annotation destination");
51       goto err2;
52     }
53     pageNum = obj1.getInt() + 1;
54     obj1.free();
55   }
56
57   // get destination type
58   a->get(1, &obj1);
59
60   // XYZ link
61   if (obj1.isName("XYZ")) {
62     kind = destXYZ;
63     a->get(2, &obj2);
64     if (obj2.isNull()) {
65       changeLeft = gFalse;
66     } else if (obj2.isNum()) {
67       changeLeft = gTrue;
68       left = obj2.getNum();
69     } else {
70       error(-1, "Bad annotation destination position");
71       goto err1;
72     }
73     obj2.free();
74     a->get(3, &obj2);
75     if (obj2.isNull()) {
76       changeTop = gFalse;
77     } else if (obj2.isNum()) {
78       changeTop = gTrue;
79       top = obj2.getNum();
80     } else {
81       error(-1, "Bad annotation destination position");
82       goto err1;
83     }
84     obj2.free();
85     a->get(4, &obj2);
86     if (obj2.isNull()) {
87       changeZoom = gFalse;
88     } else if (obj2.isNum()) {
89       changeZoom = gTrue;
90       zoom = obj2.getNum();
91     } else {
92       error(-1, "Bad annotation destination position");
93       goto err1;
94     }
95     obj2.free();
96
97   // Fit link
98   } else if (obj1.isName("Fit")) {
99     kind = destFit;
100
101   // FitH link
102   } else if (obj1.isName("FitH")) {
103     kind = destFitH;
104     if (!a->get(2, &obj2)->isNum()) {
105       error(-1, "Bad annotation destination position");
106       goto err1;
107     }
108     top = obj2.getNum();
109     obj2.free();
110
111   // FitV link
112   } else if (obj1.isName("FitV")) {
113     kind = destFitV;
114     if (!a->get(2, &obj2)->isNum()) {
115       error(-1, "Bad annotation destination position");
116       goto err1;
117     }
118     left = obj2.getNum();
119     obj2.free();
120
121   // FitR link
122   } else if (obj1.isName("FitR")) {
123     kind = destFitR;
124     if (!a->get(2, &obj2)->isNum()) {
125       error(-1, "Bad annotation destination position");
126       goto err1;
127     }
128     left = obj2.getNum();
129     obj2.free();
130     if (!a->get(3, &obj2)->isNum()) {
131       error(-1, "Bad annotation destination position");
132       goto err1;
133     }
134     bottom = obj2.getNum();
135     obj2.free();
136     if (!a->get(4, &obj2)->isNum()) {
137       error(-1, "Bad annotation destination position");
138       goto err1;
139     }
140     right = obj2.getNum();
141     obj2.free();
142     if (!a->get(5, &obj2)->isNum()) {
143       error(-1, "Bad annotation destination position");
144       goto err1;
145     }
146     top = obj2.getNum();
147     obj2.free();
148
149   // FitB link
150   } else if (obj1.isName("FitB")) {
151     kind = destFitB;
152
153   // FitBH link
154   } else if (obj1.isName("FitBH")) {
155     kind = destFitBH;
156     if (!a->get(2, &obj2)->isNum()) {
157       error(-1, "Bad annotation destination position");
158       goto err1;
159     }
160     top = obj2.getNum();
161     obj2.free();
162
163   // FitBV link
164   } else if (obj1.isName("FitBV")) {
165     kind = destFitBV;
166     if (!a->get(2, &obj2)->isNum()) {
167       error(-1, "Bad annotation destination position");
168       goto err1;
169     }
170     left = obj2.getNum();
171     obj2.free();
172
173   // unknown link kind
174   } else {
175     error(-1, "Unknown annotation destination type");
176     goto err2;
177   }
178
179   obj1.free();
180   ok = gTrue;
181   return;
182
183  err1:
184   obj2.free();
185  err2:
186   obj1.free();
187 }
188
189 LinkDest::LinkDest(LinkDest *dest) {
190   kind = dest->kind;
191   pageIsRef = dest->pageIsRef;
192   if (pageIsRef)
193     pageRef = dest->pageRef;
194   else
195     pageNum = dest->pageNum;
196   left = dest->left;
197   bottom = dest->bottom;
198   right = dest->right;
199   top = dest->top;
200   zoom = dest->zoom;
201   changeLeft = dest->changeLeft;
202   changeTop = dest->changeTop;
203   changeZoom = dest->changeZoom;
204   ok = gTrue;
205 }
206
207 //------------------------------------------------------------------------
208 // LinkGoTo
209 //------------------------------------------------------------------------
210
211 LinkGoTo::LinkGoTo(Object *destObj) {
212   dest = NULL;
213   namedDest = NULL;
214
215   // named destination
216   if (destObj->isName()) {
217     namedDest = new GString(destObj->getName());
218   } else if (destObj->isString()) {
219     namedDest = destObj->getString()->copy();
220
221   // destination dictionary
222   } else if (destObj->isArray()) {
223     dest = new LinkDest(destObj->getArray(), gTrue);
224     if (!dest->isOk()) {
225       delete dest;
226       dest = NULL;
227     }
228
229   // error
230   } else {
231     error(-1, "Illegal annotation destination");
232   }
233 }
234
235 LinkGoTo::~LinkGoTo() {
236   if (dest)
237     delete dest;
238   if (namedDest)
239     delete namedDest;
240 }
241
242 //------------------------------------------------------------------------
243 // LinkGoToR
244 //------------------------------------------------------------------------
245
246 LinkGoToR::LinkGoToR(Object *fileSpecObj, Object *destObj) {
247   dest = NULL;
248   namedDest = NULL;
249
250   // get file name
251   fileName = getFileSpecName(fileSpecObj);
252
253   // named destination
254   if (destObj->isName()) {
255     namedDest = new GString(destObj->getName());
256   } else if (destObj->isString()) {
257     namedDest = destObj->getString()->copy();
258
259   // destination dictionary
260   } else if (destObj->isArray()) {
261     dest = new LinkDest(destObj->getArray(), gFalse);
262     if (!dest->isOk()) {
263       delete dest;
264       dest = NULL;
265     }
266
267   // error
268   } else {
269     error(-1, "Illegal annotation destination");
270   }
271 }
272
273 LinkGoToR::~LinkGoToR() {
274   if (fileName)
275     delete fileName;
276   if (dest)
277     delete dest;
278   if (namedDest)
279     delete namedDest;
280 }
281
282
283 //------------------------------------------------------------------------
284 // LinkLaunch
285 //------------------------------------------------------------------------
286
287 LinkLaunch::LinkLaunch(Object *actionObj) {
288   Object obj1, obj2;
289
290   fileName = NULL;
291   params = NULL;
292
293   if (actionObj->isDict()) {
294     if (!actionObj->dictLookup("F", &obj1)->isNull()) {
295       fileName = getFileSpecName(&obj1);
296     } else {
297       obj1.free();
298       //~ This hasn't been defined by Adobe yet, so assume it looks
299       //~ just like the Win dictionary until they say otherwise.
300       if (actionObj->dictLookup("Unix", &obj1)->isDict()) {
301         obj1.dictLookup("F", &obj2);
302         fileName = getFileSpecName(&obj2);
303         obj2.free();
304         if (obj1.dictLookup("P", &obj2)->isString())
305           params = obj2.getString()->copy();
306         obj2.free();
307       } else {
308         error(-1, "Bad launch-type link action");
309       }
310     }
311     obj1.free();
312   }
313 }
314
315 LinkLaunch::~LinkLaunch() {
316   if (fileName)
317     delete fileName;
318   if (params)
319     delete params;
320 }
321
322 //------------------------------------------------------------------------
323 // LinkURI
324 //------------------------------------------------------------------------
325
326 LinkURI::LinkURI(Object *uriObj, GString *baseURI) {
327   GString *uri2;
328   int n;
329   char c;
330
331   uri = NULL;
332   if (uriObj->isString()) {
333     uri2 = uriObj->getString()->copy();
334     if (baseURI) {
335       n = strcspn(uri2->getCString(), "/:");
336       if (n == uri2->getLength() || uri2->getChar(n) == '/') {
337         uri = baseURI->copy();
338         c = uri->getChar(uri->getLength() - 1);
339         if (c == '/' || c == '?') {
340           if (uri2->getChar(0) == '/') {
341             uri2->del(0);
342           }
343         } else {
344           if (uri2->getChar(0) != '/') {
345             uri->append('/');
346           }
347         }
348         uri->append(uri2);
349         delete uri2;
350       } else {
351         uri = uri2;
352       }
353     } else {
354       uri = uri2;
355     }
356   } else {
357     error(-1, "Illegal URI-type link");
358   }
359 }
360
361 LinkURI::~LinkURI() {
362   if (uri)
363     delete uri;
364 }
365
366 //------------------------------------------------------------------------
367 // LinkNamed
368 //------------------------------------------------------------------------
369
370 LinkNamed::LinkNamed(Object *nameObj) {
371   name = NULL;
372   if (nameObj->isName()) {
373     name = new GString(nameObj->getName());
374   }
375 }
376
377 LinkNamed::~LinkNamed() {
378   if (name) {
379     delete name;
380   }
381 }
382
383 //------------------------------------------------------------------------
384 // LinkUnknown
385 //------------------------------------------------------------------------
386
387 LinkUnknown::LinkUnknown(char *action1) {
388   action = new GString(action1);
389 }
390
391 LinkUnknown::~LinkUnknown() {
392   delete action;
393 }
394
395 //------------------------------------------------------------------------
396 // Link
397 //------------------------------------------------------------------------
398
399 Link::Link(Dict *dict, GString *baseURI) {
400   Object obj1, obj2, obj3, obj4;
401   double t;
402
403   action = NULL;
404   ok = gFalse;
405
406   // get rectangle
407   if (!dict->lookup("Rect", &obj1)->isArray()) {
408     error(-1, "Annotation rectangle is wrong type");
409     goto err2;
410   }
411   if (!obj1.arrayGet(0, &obj2)->isNum()) {
412     error(-1, "Bad annotation rectangle");
413     goto err1;
414   }
415   x1 = obj2.getNum();
416   obj2.free();
417   if (!obj1.arrayGet(1, &obj2)->isNum()) {
418     error(-1, "Bad annotation rectangle");
419     goto err1;
420   }
421   y1 = obj2.getNum();
422   obj2.free();
423   if (!obj1.arrayGet(2, &obj2)->isNum()) {
424     error(-1, "Bad annotation rectangle");
425     goto err1;
426   }
427   x2 = obj2.getNum();
428   obj2.free();
429   if (!obj1.arrayGet(3, &obj2)->isNum()) {
430     error(-1, "Bad annotation rectangle");
431     goto err1;
432   }
433   y2 = obj2.getNum();
434   obj2.free();
435   obj1.free();
436   if (x1 > x2) {
437     t = x1;
438     x1 = x2;
439     x2 = t;
440   }
441   if (y1 > y2) {
442     t = y1;
443     y1 = y2;
444     y2 = t;
445   }
446
447   // get border
448   borderW = 0;
449   if (!dict->lookup("Border", &obj1)->isNull()) {
450     if (obj1.isArray() && obj1.arrayGet(2, &obj2)->isNum())
451       borderW = obj2.getNum();
452     else
453       error(-1, "Bad annotation border");
454     obj2.free();
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 = 0; i < numLinks; ++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 }