1 //========================================================================
5 // Copyright 1996-2003 Glyph & Cog, LLC
7 //========================================================================
11 #ifdef USE_GCC_PRAGMAS
12 #pragma implementation
18 #include "goo/GooString.h"
25 #include "UGooString.h"
27 //------------------------------------------------------------------------
29 //------------------------------------------------------------------------
31 LinkAction
*LinkAction::parseDest(Object
*obj
) {
34 action
= new LinkGoTo(obj
);
35 if (!action
->isOk()) {
42 LinkAction
*LinkAction::parseAction(Object
*obj
, GooString
*baseURI
) {
44 Object obj2
, obj3
, obj4
;
47 error(-1, "parseAction: Bad annotation action for URI '%s'",
48 baseURI
? baseURI
->getCString() : "NULL");
52 obj
->dictLookup("S", &obj2
);
55 if (obj2
.isName("GoTo")) {
56 obj
->dictLookup("D", &obj3
);
57 action
= new LinkGoTo(&obj3
);
61 } else if (obj2
.isName("GoToR")) {
62 obj
->dictLookup("F", &obj3
);
63 obj
->dictLookup("D", &obj4
);
64 action
= new LinkGoToR(&obj3
, &obj4
);
69 } else if (obj2
.isName("Launch")) {
70 action
= new LinkLaunch(obj
);
73 } else if (obj2
.isName("URI")) {
74 obj
->dictLookup("URI", &obj3
);
75 action
= new LinkURI(&obj3
, baseURI
);
79 } else if (obj2
.isName("Named")) {
80 obj
->dictLookup("N", &obj3
);
81 action
= new LinkNamed(&obj3
);
85 } else if (obj2
.isName("Movie")) {
86 obj
->dictLookupNF("Annot", &obj3
);
87 obj
->dictLookup("T", &obj4
);
88 action
= new LinkMovie(&obj3
, &obj4
);
93 } else if (obj2
.isName("Sound")) {
94 action
= new LinkSound(obj
);
97 } else if (obj2
.isName()) {
98 action
= new LinkUnknown(obj2
.getNameC());
100 // action is missing or wrong type
102 error(-1, "parseAction: Unknown annotation action object: URI = '%s'",
103 baseURI
? baseURI
->getCString() : "NULL");
109 if (action
&& !action
->isOk()) {
116 GooString
*LinkAction::getFileSpecName(Object
*fileSpecObj
) {
123 if (fileSpecObj
->isString()) {
124 name
= fileSpecObj
->getString()->copy();
127 } else if (fileSpecObj
->isDict()) {
129 if (!fileSpecObj
->dictLookup("DOS", &obj1
)->isString()) {
131 if (!fileSpecObj
->dictLookup("Unix", &obj1
)->isString()) {
134 fileSpecObj
->dictLookup("F", &obj1
);
136 if (obj1
.isString()) {
137 name
= obj1
.getString()->copy();
139 error(-1, "Illegal file spec in link");
145 error(-1, "Illegal file spec in link");
148 // system-dependent path manipulation
153 // "//...." --> "\...."
154 // "/x/...." --> "x:\...."
155 // "/server/share/...." --> "\\server\share\...."
156 // convert escaped slashes to slashes and unescaped slashes to backslashes
158 if (name
->getChar(0) == '/') {
159 if (name
->getLength() >= 2 && name
->getChar(1) == '/') {
162 } else if (name
->getLength() >= 2 &&
163 ((name
->getChar(1) >= 'a' && name
->getChar(1) <= 'z') ||
164 (name
->getChar(1) >= 'A' && name
->getChar(1) <= 'Z')) &&
165 (name
->getLength() == 2 || name
->getChar(2) == '/')) {
166 name
->setChar(0, name
->getChar(1));
167 name
->setChar(1, ':');
170 for (j
= 2; j
< name
->getLength(); ++j
) {
171 if (name
->getChar(j
-1) != '\\' &&
172 name
->getChar(j
) == '/') {
176 if (j
< name
->getLength()) {
177 name
->setChar(0, '\\');
178 name
->insert(0, '\\');
183 for (; i
< name
->getLength(); ++i
) {
184 if (name
->getChar(i
) == '/') {
185 name
->setChar(i
, '\\');
186 } else if (name
->getChar(i
) == '\\' &&
187 i
+1 < name
->getLength() &&
188 name
->getChar(i
+1) == '/') {
193 // no manipulation needed for Unix
200 //------------------------------------------------------------------------
202 //------------------------------------------------------------------------
204 LinkDest::LinkDest(Array
*a
) {
208 left
= bottom
= right
= top
= zoom
= 0;
212 if (a
->getLength() < 2) {
213 error(-1, "Annotation destination array is too short");
218 pageNum
= obj1
.getInt() + 1;
220 } else if (obj1
.isRef()) {
221 pageRef
.num
= obj1
.getRefNum();
222 pageRef
.gen
= obj1
.getRefGen();
225 error(-1, "Bad annotation destination");
230 // get destination type
234 if (obj1
.isName("XYZ")) {
236 if (a
->getLength() < 3) {
242 } else if (obj2
.isNum()) {
244 left
= obj2
.getNum();
246 error(-1, "Bad annotation destination position");
251 if (a
->getLength() < 4) {
257 } else if (obj2
.isNum()) {
261 error(-1, "Bad annotation destination position");
266 if (a
->getLength() < 5) {
272 } else if (obj2
.isNum()) {
274 zoom
= obj2
.getNum();
276 error(-1, "Bad annotation destination position");
283 } else if (obj1
.isName("Fit")) {
284 if (a
->getLength() < 2) {
285 error(-1, "Annotation destination array is too short");
291 } else if (obj1
.isName("FitH")) {
292 if (a
->getLength() < 3) {
293 error(-1, "Annotation destination array is too short");
297 if (!a
->get(2, &obj2
)->isNum()) {
298 error(-1, "Bad annotation destination position");
305 } else if (obj1
.isName("FitV")) {
306 if (a
->getLength() < 3) {
307 error(-1, "Annotation destination array is too short");
311 if (!a
->get(2, &obj2
)->isNum()) {
312 error(-1, "Bad annotation destination position");
315 left
= obj2
.getNum();
319 } else if (obj1
.isName("FitR")) {
320 if (a
->getLength() < 6) {
321 error(-1, "Annotation destination array is too short");
325 if (!a
->get(2, &obj2
)->isNum()) {
326 error(-1, "Bad annotation destination position");
329 left
= obj2
.getNum();
331 if (!a
->get(3, &obj2
)->isNum()) {
332 error(-1, "Bad annotation destination position");
335 bottom
= obj2
.getNum();
337 if (!a
->get(4, &obj2
)->isNum()) {
338 error(-1, "Bad annotation destination position");
341 right
= obj2
.getNum();
343 if (!a
->get(5, &obj2
)->isNum()) {
344 error(-1, "Bad annotation destination position");
351 } else if (obj1
.isName("FitB")) {
352 if (a
->getLength() < 2) {
353 error(-1, "Annotation destination array is too short");
359 } else if (obj1
.isName("FitBH")) {
360 if (a
->getLength() < 3) {
361 error(-1, "Annotation destination array is too short");
365 if (!a
->get(2, &obj2
)->isNum()) {
366 error(-1, "Bad annotation destination position");
373 } else if (obj1
.isName("FitBV")) {
374 if (a
->getLength() < 3) {
375 error(-1, "Annotation destination array is too short");
379 if (!a
->get(2, &obj2
)->isNum()) {
380 error(-1, "Bad annotation destination position");
383 left
= obj2
.getNum();
388 error(-1, "Unknown annotation destination type");
402 LinkDest::LinkDest(LinkDest
*dest
) {
404 pageIsRef
= dest
->pageIsRef
;
406 pageRef
= dest
->pageRef
;
408 pageNum
= dest
->pageNum
;
410 bottom
= dest
->bottom
;
414 changeLeft
= dest
->changeLeft
;
415 changeTop
= dest
->changeTop
;
416 changeZoom
= dest
->changeZoom
;
420 //------------------------------------------------------------------------
422 //------------------------------------------------------------------------
424 LinkGoTo::LinkGoTo(Object
*destObj
) {
429 if (destObj
->isName()) {
430 namedDest
= new UGooString(destObj
->getNameC());
431 } else if (destObj
->isString()) {
432 namedDest
= new UGooString(*destObj
->getString());
434 // destination dictionary
435 } else if (destObj
->isArray()) {
436 dest
= new LinkDest(destObj
->getArray());
444 error(-1, "Illegal annotation destination");
448 LinkGoTo::~LinkGoTo() {
455 //------------------------------------------------------------------------
457 //------------------------------------------------------------------------
459 LinkGoToR::LinkGoToR(Object
*fileSpecObj
, Object
*destObj
) {
464 fileName
= getFileSpecName(fileSpecObj
);
467 if (destObj
->isName()) {
468 namedDest
= new UGooString(destObj
->getNameC());
469 } else if (destObj
->isString()) {
470 namedDest
= new UGooString(*destObj
->getString());
472 // destination dictionary
473 } else if (destObj
->isArray()) {
474 dest
= new LinkDest(destObj
->getArray());
482 error(-1, "Illegal annotation destination");
486 LinkGoToR::~LinkGoToR() {
496 //------------------------------------------------------------------------
498 //------------------------------------------------------------------------
500 LinkLaunch::LinkLaunch(Object
*actionObj
) {
506 if (actionObj
->isDict()) {
507 if (!actionObj
->dictLookup("F", &obj1
)->isNull()) {
508 fileName
= getFileSpecName(&obj1
);
512 if (actionObj
->dictLookup("Win", &obj1
)->isDict()) {
513 obj1
.dictLookup("F", &obj2
);
514 fileName
= getFileSpecName(&obj2
);
516 if (obj1
.dictLookup("P", &obj2
)->isString()) {
517 params
= obj2
.getString()->copy();
521 error(-1, "Bad launch-type link action");
524 //~ This hasn't been defined by Adobe yet, so assume it looks
525 //~ just like the Win dictionary until they say otherwise.
526 if (actionObj
->dictLookup("Unix", &obj1
)->isDict()) {
527 obj1
.dictLookup("F", &obj2
);
528 fileName
= getFileSpecName(&obj2
);
530 if (obj1
.dictLookup("P", &obj2
)->isString()) {
531 params
= obj2
.getString()->copy();
535 error(-1, "Bad launch-type link action");
543 LinkLaunch::~LinkLaunch() {
550 //------------------------------------------------------------------------
552 //------------------------------------------------------------------------
554 LinkURI::LinkURI(Object
*uriObj
, GooString
*baseURI
) {
560 if (uriObj
->isString()) {
561 uri2
= uriObj
->getString()->copy();
562 if (baseURI
&& baseURI
->getLength() > 0) {
563 n
= strcspn(uri2
->getCString(), "/:");
564 if (n
== uri2
->getLength() || uri2
->getChar(n
) == '/') {
565 uri
= baseURI
->copy();
566 c
= uri
->getChar(uri
->getLength() - 1);
567 if (c
== '/' || c
== '?') {
568 if (uri2
->getChar(0) == '/') {
572 if (uri2
->getChar(0) != '/') {
585 error(-1, "Illegal URI-type link");
589 LinkURI::~LinkURI() {
594 //------------------------------------------------------------------------
596 //------------------------------------------------------------------------
598 LinkNamed::LinkNamed(Object
*nameObj
) {
600 if (nameObj
->isName()) {
601 name
= new GooString(nameObj
->getName());
605 LinkNamed::~LinkNamed() {
611 //------------------------------------------------------------------------
613 //------------------------------------------------------------------------
615 LinkMovie::LinkMovie(Object
*annotObj
, Object
*titleObj
) {
618 if (annotObj
->isRef()) {
619 annotRef
= annotObj
->getRef();
620 } else if (titleObj
->isString()) {
621 title
= titleObj
->getString()->copy();
623 error(-1, "Movie action is missing both the Annot and T keys");
627 LinkMovie::~LinkMovie() {
633 //------------------------------------------------------------------------
635 //------------------------------------------------------------------------
637 LinkSound::LinkSound(Object
*soundObj
) {
643 if (soundObj
->isDict())
647 soundObj
->dictLookup("Volume", &tmp
);
649 volume
= tmp
.getNum();
653 soundObj
->dictLookup("Synchronous", &tmp
);
655 sync
= tmp
.getBool();
659 soundObj
->dictLookup("Repeat", &tmp
);
661 repeat
= tmp
.getBool();
665 soundObj
->dictLookup("Mix", &tmp
);
671 soundObj
->dictLookup("Sound", &tmp
);
672 sound
= Sound::parseSound(&tmp
);
677 LinkSound::~LinkSound() {
681 //------------------------------------------------------------------------
683 //------------------------------------------------------------------------
685 LinkUnknown::LinkUnknown(char *actionA
) {
686 action
= new GooString(actionA
);
689 LinkUnknown::~LinkUnknown() {
693 //------------------------------------------------------------------------
695 //------------------------------------------------------------------------
697 LinkBorderStyle::LinkBorderStyle(LinkBorderType typeA
, double widthA
,
698 double *dashA
, int dashLengthA
,
699 double rA
, double gA
, double bA
) {
703 dashLength
= dashLengthA
;
709 LinkBorderStyle::~LinkBorderStyle() {
715 //------------------------------------------------------------------------
717 //------------------------------------------------------------------------
719 Link::Link(Dict
*dict
, GooString
*baseURI
) {
720 Object obj1
, obj2
, obj3
;
721 LinkBorderType borderType
;
724 int borderDashLength
;
725 double borderR
, borderG
, borderB
;
734 if (!dict
->lookup("Rect", &obj1
)->isArray()) {
735 error(-1, "Annotation rectangle is wrong type");
738 if (!obj1
.arrayGet(0, &obj2
)->isNum()) {
739 error(-1, "Bad annotation rectangle");
744 if (!obj1
.arrayGet(1, &obj2
)->isNum()) {
745 error(-1, "Bad annotation rectangle");
750 if (!obj1
.arrayGet(2, &obj2
)->isNum()) {
751 error(-1, "Bad annotation rectangle");
756 if (!obj1
.arrayGet(3, &obj2
)->isNum()) {
757 error(-1, "Bad annotation rectangle");
774 // get the border style info
775 borderType
= linkBorderSolid
;
778 borderDashLength
= 0;
782 if (dict
->lookup("BS", &obj1
)->isDict()) {
783 if (obj1
.dictLookup("S", &obj2
)->isName()) {
784 if (obj2
.isName("S")) {
785 borderType
= linkBorderSolid
;
786 } else if (obj2
.isName("D")) {
787 borderType
= linkBorderDashed
;
788 } else if (obj2
.isName("B")) {
789 borderType
= linkBorderEmbossed
;
790 } else if (obj2
.isName("I")) {
791 borderType
= linkBorderEngraved
;
792 } else if (obj2
.isName("U")) {
793 borderType
= linkBorderUnderlined
;
797 if (obj1
.dictLookup("W", &obj2
)->isNum()) {
798 borderWidth
= obj2
.getNum();
801 if (obj1
.dictLookup("D", &obj2
)->isArray()) {
802 borderDashLength
= obj2
.arrayGetLength();
803 borderDash
= (double *)gmallocn(borderDashLength
, sizeof(double));
804 for (i
= 0; i
< borderDashLength
; ++i
) {
805 if (obj2
.arrayGet(i
, &obj3
)->isNum()) {
806 borderDash
[i
] = obj3
.getNum();
816 if (dict
->lookup("Border", &obj1
)->isArray()) {
817 if (obj1
.arrayGetLength() >= 3) {
818 if (obj1
.arrayGet(2, &obj2
)->isNum()) {
819 borderWidth
= obj2
.getNum();
822 if (obj1
.arrayGetLength() >= 4) {
823 if (obj1
.arrayGet(3, &obj2
)->isArray()) {
824 borderType
= linkBorderDashed
;
825 borderDashLength
= obj2
.arrayGetLength();
826 borderDash
= (double *)gmallocn(borderDashLength
, sizeof(double));
827 for (i
= 0; i
< borderDashLength
; ++i
) {
828 if (obj2
.arrayGet(i
, &obj3
)->isNum()) {
829 borderDash
[i
] = obj3
.getNum();
836 // Adobe draws no border at all if the last element is of
846 if (dict
->lookup("C", &obj1
)->isArray() && obj1
.arrayGetLength() == 3) {
847 if (obj1
.arrayGet(0, &obj2
)->isNum()) {
848 borderR
= obj2
.getNum();
851 if (obj1
.arrayGet(1, &obj2
)->isNum()) {
852 borderG
= obj2
.getNum();
855 if (obj1
.arrayGet(2, &obj2
)->isNum()) {
856 borderB
= obj2
.getNum();
861 borderStyle
= new LinkBorderStyle(borderType
, borderWidth
,
862 borderDash
, borderDashLength
,
863 borderR
, borderG
, borderB
);
865 // look for destination
866 if (!dict
->lookup("Dest", &obj1
)->isNull()) {
867 action
= LinkAction::parseDest(&obj1
);
872 if (dict
->lookup("A", &obj1
)->isDict()) {
873 action
= LinkAction::parseAction(&obj1
, baseURI
);
878 // check for bad action
900 //------------------------------------------------------------------------
902 //------------------------------------------------------------------------
904 Links::Links(Object
*annots
, GooString
*baseURI
) {
914 if (annots
->isArray()) {
915 for (i
= 0; i
< annots
->arrayGetLength(); ++i
) {
916 if (annots
->arrayGet(i
, &obj1
)->isDict()) {
917 if (obj1
.dictLookup("Subtype", &obj2
)->isName("Link")) {
918 link
= new Link(obj1
.getDict(), baseURI
);
920 if (numLinks
>= size
) {
922 links
= (Link
**)greallocn(links
, size
, sizeof(Link
*));
924 links
[numLinks
++] = link
;
939 for (i
= 0; i
< numLinks
; ++i
)
944 LinkAction
*Links::find(double x
, double y
) const {
947 for (i
= numLinks
- 1; i
>= 0; --i
) {
948 if (links
[i
]->inRect(x
, y
)) {
949 return links
[i
]->getAction();
955 GBool
Links::onLink(double x
, double y
) const {
958 for (i
= 0; i
< numLinks
; ++i
) {
959 if (links
[i
]->inRect(x
, y
))