From 1fb149d7560d5477e49a55765eff172b8916b607 Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Fri, 23 Mar 2018 13:05:07 +0100 Subject: [PATCH] Add support for Next actions following an action Next actions are action dictionaries or an array of action dictonaries. "Next" is an entry in the general action dictionary. These actions are supposed to be performed after each other. So that a single button press can for example both trigger a Hide action and a JavaScript action. --- poppler/Link.cc | 77 +++++++++++++++++++++++++++++++++++++++++- poppler/Link.h | 18 ++++++++-- qt5/src/poppler-link-private.h | 6 ++++ qt5/src/poppler-link.cc | 10 ++++++ qt5/src/poppler-link.h | 19 ++++++++++- qt5/src/poppler-page.cc | 14 ++++++++ 6 files changed, 139 insertions(+), 5 deletions(-) diff --git a/poppler/Link.cc b/poppler/Link.cc index 64f17f99..33fec46e 100644 --- a/poppler/Link.cc +++ b/poppler/Link.cc @@ -52,6 +52,14 @@ //------------------------------------------------------------------------ // LinkAction //------------------------------------------------------------------------ +LinkAction::LinkAction(): nextActionList(nullptr) { +} + +LinkAction::~LinkAction() { + if (nextActionList) { + delete nextActionList; + } +} LinkAction *LinkAction::parseDest(const Object *obj) { LinkAction *action; @@ -64,7 +72,8 @@ LinkAction *LinkAction::parseDest(const Object *obj) { return action; } -LinkAction *LinkAction::parseAction(const Object *obj, const GooString *baseURI) { +LinkAction *LinkAction::parseAction(const Object *obj, const GooString *baseURI, + const std::set *usedNextActions) { LinkAction *action; if (!obj->isDict()) { @@ -140,9 +149,75 @@ LinkAction *LinkAction::parseAction(const Object *obj, const GooString *baseURI) delete action; return nullptr; } + + if (!action) { + return nullptr; + } + + // parse the next actions + Object nextObj = obj->dictLookup("Next"); + GooList *actionList = nullptr; + if (nextObj.isDict()) { + + // Prevent circles in the tree by checking the ref against used refs in + // our current tree branch. + const Object nextRefObj = obj->dictLookupNF("Next"); + std::set usedNextActionsAux = usedNextActions ? *usedNextActions : std::set (); + if (nextRefObj.isRef()) { + const Ref ref = nextRefObj.getRef(); + if (usedNextActionsAux.find(ref.num) != usedNextActionsAux.end()) { + error(errSyntaxWarning, -1, "parseAction: Circular next actions detected."); + return action; + } + usedNextActionsAux.insert(ref.num); + } + + actionList = new GooList(1); + actionList->append(parseAction(&nextObj, nullptr, &usedNextActionsAux)); + } else if (nextObj.isArray()) { + Array *a = nextObj.getArray(); + int n = a->getLength(); + actionList = new GooList(n); + for (int i = 0; i < n; ++i) { + Object obj3 = a->get(i); + if (!obj3.isDict()) { + error(errSyntaxWarning, -1, "parseAction: Next array does not contain only dicts"); + continue; + } + + // Similar circle check as above. + std::set usedNextActionsAux = usedNextActions ? *usedNextActions : std::set (); + const Object obj3Ref = a->getNF(i); + + if (obj3Ref.isRef()) { + const Ref ref = obj3Ref.getRef(); + if (usedNextActionsAux.find(ref.num) != usedNextActionsAux.end()) { + error(errSyntaxWarning, -1, "parseAction: Circular next actions detected in array."); + return action; + } + usedNextActionsAux.insert(ref.num); + } + + actionList->append(parseAction(&obj3, nullptr, &usedNextActionsAux)); + } + } + + action->setNextActions(actionList); + return action; } +GooList *LinkAction::nextActions() const { + return nextActionList; +} + +void LinkAction::setNextActions(GooList *actions) { + if (nextActionList) { + delete nextActionList; + } + nextActionList = actions; +} + //------------------------------------------------------------------------ // LinkDest //------------------------------------------------------------------------ diff --git a/poppler/Link.h b/poppler/Link.h index 6142f927..2452c04d 100644 --- a/poppler/Link.h +++ b/poppler/Link.h @@ -34,6 +34,7 @@ #endif #include "Object.h" +#include class GooString; class GooList; @@ -66,12 +67,12 @@ enum LinkActionKind { class LinkAction { public: - LinkAction() = default; + LinkAction(); LinkAction(const LinkAction &) = delete; LinkAction& operator=(const LinkAction &other) = delete; // Destructor. - virtual ~LinkAction() {} + virtual ~LinkAction(); // Was the LinkAction created successfully? virtual GBool isOk() const = 0; @@ -83,7 +84,18 @@ public: static LinkAction *parseDest(const Object *obj); // Parse an action dictionary. - static LinkAction *parseAction(const Object *obj, const GooString *baseURI = NULL); + static LinkAction *parseAction(const Object *obj, const GooString *baseURI = nullptr, + const std::set *usedNextActions = nullptr); + + // A List of the next actions to execute in order. + // The list contains pointer to LinkAction objects. + GooList *nextActions() const; + // Sets the next action list. Takes ownership of the actions. + void setNextActions(GooList *actions); + +private: + + GooList *nextActionList; }; //------------------------------------------------------------------------ diff --git a/qt5/src/poppler-link-private.h b/qt5/src/poppler-link-private.h index 3ef29495..0f3d931f 100644 --- a/qt5/src/poppler-link-private.h +++ b/qt5/src/poppler-link-private.h @@ -23,6 +23,8 @@ class LinkOCGState; namespace Poppler { +class Link; + class LinkPrivate { public: @@ -33,12 +35,16 @@ public: virtual ~LinkPrivate() { + for (Link *l: nextLinks) { + delete l; + } } LinkPrivate(const LinkPrivate &) = delete; LinkPrivate& operator=(const LinkPrivate &) = delete; QRectF linkArea; + QVector nextLinks; }; diff --git a/qt5/src/poppler-link.cc b/qt5/src/poppler-link.cc index 1279e0b2..b43e8017 100644 --- a/qt5/src/poppler-link.cc +++ b/qt5/src/poppler-link.cc @@ -427,7 +427,17 @@ class LinkMoviePrivate : public LinkPrivate Q_D( const Link ); return d->linkArea; } + + QVector< Link * > Link::nextLinks() const + { + return d_ptr->nextLinks; + } + void Link::setNextLinks( const QVector< Link * > &links ) const + { + d_ptr->nextLinks = links; + } + // LinkGoto LinkGoto::LinkGoto( const QRectF &linkArea, QString extFileName, const LinkDestination & destination ) : Link( *new LinkGotoPrivate( linkArea, destination ) ) diff --git a/qt5/src/poppler-link.h b/qt5/src/poppler-link.h index 3bb4bebe..35002e49 100644 --- a/qt5/src/poppler-link.h +++ b/qt5/src/poppler-link.h @@ -28,6 +28,7 @@ #include #include #include +#include #include "poppler-export.h" struct Ref; @@ -219,7 +220,23 @@ class POPPLER_QT5_EXPORT Link * a general action. The area is given in 0..1 range */ QRectF linkArea() const; - + + /** + * Get the next links to be activiated / executed after this link. + * + * \since 0.64 + */ + QVector nextLinks() const; + + /** + * Set the next links to be activated / executed after this link. + * + * Takes ownership of the link objects pointed to in the vector. + * + * \since 0.64 + */ + void setNextLinks( const QVector< Link * > &links ) const; + protected: /// \cond PRIVATE Link( LinkPrivate &dd ); diff --git a/qt5/src/poppler-page.cc b/qt5/src/poppler-page.cc index 355d5e84..e0a1f16e 100644 --- a/qt5/src/poppler-page.cc +++ b/qt5/src/poppler-page.cc @@ -357,6 +357,20 @@ Link* PageData::convertLinkActionToLink(::LinkAction * a, DocumentData *parentDo break; } + if ( popplerLink ) + { + GooList *nextActions = a->nextActions(); + if ( nextActions ) + { + QVector links; + for ( int i = 0, N = nextActions->getLength(); i < N; ++i ) + { + links << convertLinkActionToLink( static_cast< ::LinkAction * >( nextActions->get( i ) ), parentDoc, linkArea ); + } + popplerLink->setNextLinks( links ); + } + } + return popplerLink; } -- 2.11.0