1 //========================================================================
5 // Copyright 1996-2003 Glyph & Cog, LLC
7 //========================================================================
9 //========================================================================
11 // Modified under the Poppler project - http://poppler.freedesktop.org
13 // All changes made under the Poppler project to this file are licensed
14 // under GPL version 2 or later
16 // Copyright (C) 2006, 2008 Pino Toscano <pino@kde.org>
17 // Copyright (C) 2007, 2010, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
18 // Copyright (C) 2008 Hugo Mercier <hmercier31@gmail.com>
19 // Copyright (C) 2008-2010, 2012-2014 Albert Astals Cid <aacid@kde.org>
20 // Copyright (C) 2009 Kovid Goyal <kovid@kovidgoyal.net>
21 // Copyright (C) 2009 Ilya Gorenbein <igorenbein@finjan.com>
22 // Copyright (C) 2012 Tobias Koening <tobias.koenig@kdab.com>
24 // To see a description of the changes please see the Changelog file that
25 // came with your tarball or type make ChangeLog if you are building from git
27 //========================================================================
31 #ifdef USE_GCC_PRAGMAS
32 #pragma implementation
38 #include "goo/GooString.h"
39 #include "goo/GooList.h"
47 #include "Rendition.h"
50 //------------------------------------------------------------------------
52 //------------------------------------------------------------------------
54 LinkAction
*LinkAction::parseDest(Object
*obj
) {
57 action
= new LinkGoTo(obj
);
58 if (!action
->isOk()) {
65 LinkAction
*LinkAction::parseAction(Object
*obj
, GooString
*baseURI
) {
67 Object obj2
, obj3
, obj4
;
70 error(errSyntaxWarning
, -1, "parseAction: Bad annotation action for URI '{0:s}'",
71 baseURI
? baseURI
->getCString() : "NULL");
75 obj
->dictLookup("S", &obj2
);
78 if (obj2
.isName("GoTo")) {
79 obj
->dictLookup("D", &obj3
);
80 action
= new LinkGoTo(&obj3
);
84 } else if (obj2
.isName("GoToR")) {
85 obj
->dictLookup("F", &obj3
);
86 obj
->dictLookup("D", &obj4
);
87 action
= new LinkGoToR(&obj3
, &obj4
);
92 } else if (obj2
.isName("Launch")) {
93 action
= new LinkLaunch(obj
);
96 } else if (obj2
.isName("URI")) {
97 obj
->dictLookup("URI", &obj3
);
98 action
= new LinkURI(&obj3
, baseURI
);
102 } else if (obj2
.isName("Named")) {
103 obj
->dictLookup("N", &obj3
);
104 action
= new LinkNamed(&obj3
);
108 } else if (obj2
.isName("Movie")) {
109 action
= new LinkMovie(obj
);
112 } else if (obj2
.isName("Rendition")) {
113 action
= new LinkRendition(obj
);
116 } else if (obj2
.isName("Sound")) {
117 action
= new LinkSound(obj
);
120 } else if (obj2
.isName("JavaScript")) {
121 obj
->dictLookup("JS", &obj3
);
122 action
= new LinkJavaScript(&obj3
);
125 // Set-OCG-State action
126 } else if (obj2
.isName("SetOCGState")) {
127 action
= new LinkOCGState(obj
);
130 } else if (obj2
.isName()) {
131 action
= new LinkUnknown(obj2
.getName());
133 // action is missing or wrong type
135 error(errSyntaxWarning
, -1, "parseAction: Unknown annotation action object: URI = '{0:s}'",
136 baseURI
? baseURI
->getCString() : "NULL");
142 if (action
&& !action
->isOk()) {
149 //------------------------------------------------------------------------
151 //------------------------------------------------------------------------
153 LinkDest::LinkDest(Array
*a
) {
157 left
= bottom
= right
= top
= zoom
= 0;
158 changeLeft
= changeTop
= changeZoom
= gFalse
;
162 if (a
->getLength() < 2) {
163 error(errSyntaxWarning
, -1, "Annotation destination array is too short");
168 pageNum
= obj1
.getInt() + 1;
170 } else if (obj1
.isRef()) {
171 pageRef
.num
= obj1
.getRefNum();
172 pageRef
.gen
= obj1
.getRefGen();
175 error(errSyntaxWarning
, -1, "Bad annotation destination");
180 // get destination type
184 if (obj1
.isName("XYZ")) {
186 if (a
->getLength() < 3) {
192 } else if (obj2
.isNum()) {
194 left
= obj2
.getNum();
196 error(errSyntaxWarning
, -1, "Bad annotation destination position");
201 if (a
->getLength() < 4) {
207 } else if (obj2
.isNum()) {
211 error(errSyntaxWarning
, -1, "Bad annotation destination position");
216 if (a
->getLength() < 5) {
222 } else if (obj2
.isNum()) {
223 zoom
= obj2
.getNum();
224 changeZoom
= (zoom
== 0) ? gFalse
: gTrue
;
226 error(errSyntaxWarning
, -1, "Bad annotation destination position");
233 } else if (obj1
.isName("Fit")) {
234 if (a
->getLength() < 2) {
235 error(errSyntaxWarning
, -1, "Annotation destination array is too short");
241 } else if (obj1
.isName("FitH")) {
242 if (a
->getLength() < 3) {
243 error(errSyntaxWarning
, -1, "Annotation destination array is too short");
250 } else if (obj2
.isNum()) {
254 error(errSyntaxWarning
, -1, "Bad annotation destination position");
260 } else if (obj1
.isName("FitV")) {
261 if (a
->getLength() < 3) {
262 error(errSyntaxWarning
, -1, "Annotation destination array is too short");
269 } else if (obj2
.isNum()) {
271 left
= obj2
.getNum();
273 error(errSyntaxWarning
, -1, "Bad annotation destination position");
279 } else if (obj1
.isName("FitR")) {
280 if (a
->getLength() < 6) {
281 error(errSyntaxWarning
, -1, "Annotation destination array is too short");
285 if (a
->get(2, &obj2
)->isNum()) {
286 left
= obj2
.getNum();
288 error(errSyntaxWarning
, -1, "Bad annotation destination position");
292 if (a
->get(3, &obj2
)->isNum()) {
293 bottom
= obj2
.getNum();
295 error(errSyntaxWarning
, -1, "Bad annotation destination position");
299 if (a
->get(4, &obj2
)->isNum()) {
300 right
= obj2
.getNum();
302 error(errSyntaxWarning
, -1, "Bad annotation destination position");
306 if (a
->get(5, &obj2
)->isNum()) {
309 error(errSyntaxWarning
, -1, "Bad annotation destination position");
315 } else if (obj1
.isName("FitB")) {
316 if (a
->getLength() < 2) {
317 error(errSyntaxWarning
, -1, "Annotation destination array is too short");
323 } else if (obj1
.isName("FitBH")) {
324 if (a
->getLength() < 3) {
325 error(errSyntaxWarning
, -1, "Annotation destination array is too short");
332 } else if (obj2
.isNum()) {
336 error(errSyntaxWarning
, -1, "Bad annotation destination position");
342 } else if (obj1
.isName("FitBV")) {
343 if (a
->getLength() < 3) {
344 error(errSyntaxWarning
, -1, "Annotation destination array is too short");
351 } else if (obj2
.isNum()) {
353 left
= obj2
.getNum();
355 error(errSyntaxWarning
, -1, "Bad annotation destination position");
362 error(errSyntaxWarning
, -1, "Unknown annotation destination type");
376 LinkDest::LinkDest(LinkDest
*dest
) {
378 pageIsRef
= dest
->pageIsRef
;
380 pageRef
= dest
->pageRef
;
382 pageNum
= dest
->pageNum
;
384 bottom
= dest
->bottom
;
388 changeLeft
= dest
->changeLeft
;
389 changeTop
= dest
->changeTop
;
390 changeZoom
= dest
->changeZoom
;
394 //------------------------------------------------------------------------
396 //------------------------------------------------------------------------
398 LinkGoTo::LinkGoTo(Object
*destObj
) {
403 if (destObj
->isName()) {
404 namedDest
= new GooString(destObj
->getName());
405 } else if (destObj
->isString()) {
406 namedDest
= destObj
->getString()->copy();
408 // destination dictionary
409 } else if (destObj
->isArray()) {
410 dest
= new LinkDest(destObj
->getArray());
418 error(errSyntaxWarning
, -1, "Illegal annotation destination");
422 LinkGoTo::~LinkGoTo() {
429 //------------------------------------------------------------------------
431 //------------------------------------------------------------------------
433 LinkGoToR::LinkGoToR(Object
*fileSpecObj
, Object
*destObj
) {
440 if (getFileSpecNameForPlatform (fileSpecObj
, &obj1
)) {
441 fileName
= obj1
.getString()->copy();
446 if (destObj
->isName()) {
447 namedDest
= new GooString(destObj
->getName());
448 } else if (destObj
->isString()) {
449 namedDest
= destObj
->getString()->copy();
451 // destination dictionary
452 } else if (destObj
->isArray()) {
453 dest
= new LinkDest(destObj
->getArray());
461 error(errSyntaxWarning
, -1, "Illegal annotation destination");
465 LinkGoToR::~LinkGoToR() {
475 //------------------------------------------------------------------------
477 //------------------------------------------------------------------------
479 LinkLaunch::LinkLaunch(Object
*actionObj
) {
480 Object obj1
, obj2
, obj3
;
485 if (actionObj
->isDict()) {
486 if (!actionObj
->dictLookup("F", &obj1
)->isNull()) {
487 if (getFileSpecNameForPlatform (&obj1
, &obj3
)) {
488 fileName
= obj3
.getString()->copy();
494 if (actionObj
->dictLookup("Win", &obj1
)->isDict()) {
495 obj1
.dictLookup("F", &obj2
);
496 if (getFileSpecNameForPlatform (&obj2
, &obj3
)) {
497 fileName
= obj3
.getString()->copy();
501 if (obj1
.dictLookup("P", &obj2
)->isString()) {
502 params
= obj2
.getString()->copy();
506 error(errSyntaxWarning
, -1, "Bad launch-type link action");
509 //~ This hasn't been defined by Adobe yet, so assume it looks
510 //~ just like the Win dictionary until they say otherwise.
511 if (actionObj
->dictLookup("Unix", &obj1
)->isDict()) {
512 obj1
.dictLookup("F", &obj2
);
513 if (getFileSpecNameForPlatform (&obj2
, &obj3
)) {
514 fileName
= obj3
.getString()->copy();
518 if (obj1
.dictLookup("P", &obj2
)->isString()) {
519 params
= obj2
.getString()->copy();
523 error(errSyntaxWarning
, -1, "Bad launch-type link action");
531 LinkLaunch::~LinkLaunch() {
538 //------------------------------------------------------------------------
540 //------------------------------------------------------------------------
542 LinkURI::LinkURI(Object
*uriObj
, GooString
*baseURI
) {
548 if (uriObj
->isString()) {
549 uri2
= uriObj
->getString();
550 n
= (int)strcspn(uri2
->getCString(), "/:");
551 if (n
< uri2
->getLength() && uri2
->getChar(n
) == ':') {
554 } else if (!uri2
->cmpN("www.", 4)) {
555 // "www.[...]" without the leading "http://"
556 uri
= new GooString("http://");
561 uri
= baseURI
->copy();
562 if (uri
->getLength() > 0) {
563 c
= uri
->getChar(uri
->getLength() - 1);
564 if (c
!= '/' && c
!= '?') {
568 if (uri2
->getChar(0) == '/') {
569 uri
->append(uri2
->getCString() + 1, uri2
->getLength() - 1);
578 error(errSyntaxWarning
, -1, "Illegal URI-type link");
582 LinkURI::~LinkURI() {
587 //------------------------------------------------------------------------
589 //------------------------------------------------------------------------
591 LinkNamed::LinkNamed(Object
*nameObj
) {
593 if (nameObj
->isName()) {
594 name
= new GooString(nameObj
->getName());
598 LinkNamed::~LinkNamed() {
604 //------------------------------------------------------------------------
606 //------------------------------------------------------------------------
608 LinkMovie::LinkMovie(Object
*obj
) {
613 if (obj
->dictLookupNF("Annotation", &tmp
)->isRef()) {
614 annotRef
= tmp
.getRef();
618 if (obj
->dictLookup("T", &tmp
)->isString()) {
619 annotTitle
= tmp
.getString()->copy();
623 if ((annotTitle
== NULL
) && (annotRef
.num
== -1)) {
624 error(errSyntaxError
, -1,
625 "Movie action is missing both the Annot and T keys");
628 if (obj
->dictLookup("Operation", &tmp
)->isName()) {
629 char *name
= tmp
.getName();
631 if (!strcmp(name
, "Play")) {
632 operation
= operationTypePlay
;
634 else if (!strcmp(name
, "Stop")) {
635 operation
= operationTypeStop
;
637 else if (!strcmp(name
, "Pause")) {
638 operation
= operationTypePause
;
640 else if (!strcmp(name
, "Resume")) {
641 operation
= operationTypeResume
;
647 LinkMovie::~LinkMovie() {
653 //------------------------------------------------------------------------
655 //------------------------------------------------------------------------
657 LinkSound::LinkSound(Object
*soundObj
) {
663 if (soundObj
->isDict())
667 soundObj
->dictLookup("Volume", &tmp
);
669 volume
= tmp
.getNum();
673 soundObj
->dictLookup("Synchronous", &tmp
);
675 sync
= tmp
.getBool();
679 soundObj
->dictLookup("Repeat", &tmp
);
681 repeat
= tmp
.getBool();
685 soundObj
->dictLookup("Mix", &tmp
);
691 soundObj
->dictLookup("Sound", &tmp
);
692 sound
= Sound::parseSound(&tmp
);
697 LinkSound::~LinkSound() {
701 //------------------------------------------------------------------------
703 //------------------------------------------------------------------------
705 LinkRendition::LinkRendition(Object
*obj
) {
706 operation
= NoRendition
;
709 int operationCode
= -1;
714 if (!obj
->dictLookup("JS", &tmp
)->isNull()) {
715 if (tmp
.isString()) {
716 js
= new GooString(tmp
.getString());
717 } else if (tmp
.isStream()) {
718 Stream
*stream
= tmp
.getStream();
719 js
= new GooString();
720 stream
->fillGooString(js
);
722 error(errSyntaxWarning
, -1, "Invalid Rendition Action: JS not string or stream");
727 if (obj
->dictLookup("OP", &tmp
)->isInt()) {
728 operationCode
= tmp
.getInt();
729 if (!js
&& (operationCode
< 0 || operationCode
> 4)) {
730 error(errSyntaxWarning
, -1, "Invalid Rendition Action: unrecognized operation valued: {0:d}", operationCode
);
734 // retrieve rendition object
735 if (obj
->dictLookup("R", &renditionObj
)->isDict()) {
736 media
= new MediaRendition(&renditionObj
);
737 } else if (operationCode
== 0 || operationCode
== 4) {
738 error(errSyntaxWarning
, -1, "Invalid Rendition Action: no R field with op = {0:d}", operationCode
);
742 if (!obj
->dictLookupNF("AN", &screenRef
)->isRef() && operation
>= 0 && operation
<= 4) {
743 error(errSyntaxWarning
, -1, "Invalid Rendition Action: no AN field with op = {0:d}", operationCode
);
748 switch (operationCode
) {
750 operation
= PlayRendition
;
753 operation
= StopRendition
;
756 operation
= PauseRendition
;
759 operation
= ResumeRendition
;
762 operation
= PlayRendition
;
766 error(errSyntaxWarning
, -1, "Invalid Rendition action: no OP or JS field defined");
772 LinkRendition::~LinkRendition() {
783 //------------------------------------------------------------------------
785 //------------------------------------------------------------------------
787 LinkJavaScript::LinkJavaScript(Object
*jsObj
) {
790 if (jsObj
->isString()) {
791 js
= new GooString(jsObj
->getString());
793 else if (jsObj
->isStream()) {
794 Stream
*stream
= jsObj
->getStream();
795 js
= new GooString();
796 stream
->fillGooString(js
);
800 LinkJavaScript::~LinkJavaScript() {
806 //------------------------------------------------------------------------
808 //------------------------------------------------------------------------
809 LinkOCGState::LinkOCGState(Object
*obj
) {
812 stateList
= new GooList();
815 if (obj
->dictLookup("State", &obj1
)->isArray()) {
816 StateList
*stList
= NULL
;
818 for (int i
= 0; i
< obj1
.arrayGetLength(); ++i
) {
821 obj1
.arrayGetNF(i
, &obj2
);
824 stateList
->append(stList
);
826 char *name
= obj2
.getName();
827 stList
= new StateList();
828 stList
->list
= new GooList();
829 if (!strcmp (name
, "ON")) {
831 } else if (!strcmp (name
, "OFF")) {
833 } else if (!strcmp (name
, "Toggle")) {
836 error(errSyntaxWarning
, -1, "Invalid name '{0:s}' in OCG Action state array", name
);
840 } else if (obj2
.isRef()) {
842 Ref ocgRef
= obj2
.getRef();
843 Ref
*item
= new Ref();
844 item
->num
= ocgRef
.num
;
845 item
->gen
= ocgRef
.gen
;
846 stList
->list
->append(item
);
848 error(errSyntaxWarning
, -1, "Invalid OCG Action State array, expected name instead of ref");
851 error(errSyntaxWarning
, -1, "Invalid item in OCG Action State array");
855 // Add the last group
857 stateList
->append(stList
);
859 error(errSyntaxWarning
, -1, "Invalid OCGState action");
865 if (obj
->dictLookup("PreserveRB", &obj1
)->isBool()) {
866 preserveRB
= obj1
.getBool();
871 LinkOCGState::~LinkOCGState() {
873 deleteGooList(stateList
, StateList
);
876 LinkOCGState::StateList::~StateList() {
878 deleteGooList(list
, Ref
);
881 //------------------------------------------------------------------------
883 //------------------------------------------------------------------------
885 LinkUnknown::LinkUnknown(char *actionA
) {
886 action
= new GooString(actionA
);
889 LinkUnknown::~LinkUnknown() {
893 //------------------------------------------------------------------------
895 //------------------------------------------------------------------------
897 Links::Links(Annots
*annots
) {
908 for (i
= 0; i
< annots
->getNumAnnots(); ++i
) {
909 Annot
*annot
= annots
->getAnnot(i
);
911 if (annot
->getType() != Annot::typeLink
)
914 if (numLinks
>= size
) {
916 links
= (AnnotLink
**)greallocn(links
, size
, sizeof(AnnotLink
*));
919 links
[numLinks
++] = static_cast<AnnotLink
*>(annot
);
926 for (i
= 0; i
< numLinks
; ++i
)
927 links
[i
]->decRefCnt();
932 LinkAction
*Links::find(double x
, double y
) const {
935 for (i
= numLinks
- 1; i
>= 0; --i
) {
936 if (links
[i
]->inRect(x
, y
)) {
937 return links
[i
]->getAction();
943 GBool
Links::onLink(double x
, double y
) const {
946 for (i
= 0; i
< numLinks
; ++i
) {
947 if (links
[i
]->inRect(x
, y
))