From 8b603c174dc288dcd83e19afb075853e459a82a7 Mon Sep 17 00:00:00 2001 From: Tobias Koenig Date: Tue, 4 Aug 2015 11:11:29 +0200 Subject: [PATCH] Add basic support for RichMedia annotations in PDF files That patch extracts the video file, which is defined in a rich media annotation as parameter for the flash player, and uses the normal multimedia player, to playback the video file. This feature requires poppler-qt5 in version 0.36. FEATURE: 326230 REVIEW: 124612 --- cmake/modules/FindPoppler.cmake | 12 ++ core/annotations.cpp | 108 ++++++++++++++++++ core/annotations.h | 63 ++++++++++ generators/poppler/annots.cpp | 22 ++++ .../poppler/config-okular-poppler.h.cmake | 3 + generators/poppler/generator_pdf.cpp | 96 ++++++++++++++++ ui/annotationpopup.cpp | 68 ++++++++--- ui/guiutils.cpp | 3 + ui/pageview.cpp | 18 +++ ui/presentationwidget.cpp | 19 +++ 10 files changed, 394 insertions(+), 18 deletions(-) diff --git a/cmake/modules/FindPoppler.cmake b/cmake/modules/FindPoppler.cmake index 98895ec96..436296703 100644 --- a/cmake/modules/FindPoppler.cmake +++ b/cmake/modules/FindPoppler.cmake @@ -79,12 +79,24 @@ int main() } " HAVE_POPPLER_0_28) +check_cxx_source_compiles(" +#include +int main() +{ + Poppler::Page *p = 0; + p->annotations( QSet() << Poppler::Annotation::ARichMedia ); + return 0; +} +" HAVE_POPPLER_0_36) + set(CMAKE_REQUIRED_INCLUDES) set(CMAKE_REQUIRED_LIBRARIES) if (HAVE_POPPLER_0_28) set(popplerVersionMessage "0.28") elseif (HAVE_POPPLER_0_24) set(popplerVersionMessage "0.24") + elseif (HAVE_POPPLER_0_36) + set(popplerVersionMessage "0.36") endif () if (NOT Poppler_FIND_QUIETLY) message(STATUS "Found Poppler-Qt5: ${POPPLER_LIBRARY}, (>= ${popplerVersionMessage})") diff --git a/core/annotations.cpp b/core/annotations.cpp index ac45a5ad0..2461ef27f 100644 --- a/core/annotations.cpp +++ b/core/annotations.cpp @@ -2935,3 +2935,111 @@ Action* WidgetAnnotation::additionalAction( AdditionalActionType type ) const else return d->m_additionalActions.value( type ); } + +/** RichMediaAnnotation [Annotation] */ + +class Okular::RichMediaAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + RichMediaAnnotationPrivate(); + ~RichMediaAnnotationPrivate(); + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + // data fields + Movie *movie; + EmbeddedFile *embeddedFile; +}; + +RichMediaAnnotationPrivate::RichMediaAnnotationPrivate() + : movie( 0 ), embeddedFile( 0 ) +{ +} + +RichMediaAnnotationPrivate::~RichMediaAnnotationPrivate() +{ + delete movie; + delete embeddedFile; +} + +void RichMediaAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + + // loop through the whole children looking for a 'richMedia' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "richMedia" ) + continue; + + // loading complete + break; + } +} + +AnnotationPrivate* RichMediaAnnotationPrivate::getNewAnnotationPrivate() +{ + return new RichMediaAnnotationPrivate(); +} + +RichMediaAnnotation::RichMediaAnnotation() + : Annotation( *new RichMediaAnnotationPrivate() ) +{ +} + +RichMediaAnnotation::RichMediaAnnotation( const QDomNode & node ) + : Annotation( *new RichMediaAnnotationPrivate, node ) +{ +} + +RichMediaAnnotation::~RichMediaAnnotation() +{ +} + +void RichMediaAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [richMedia] element + QDomElement movieElement = document.createElement( "richMedia" ); + node.appendChild( movieElement ); +} + +Annotation::SubType RichMediaAnnotation::subType() const +{ + return ARichMedia; +} + +void RichMediaAnnotation::setMovie( Movie *movie ) +{ + Q_D( RichMediaAnnotation ); + + delete d->movie; + d->movie = movie; +} + +Movie* RichMediaAnnotation::movie() const +{ + Q_D( const RichMediaAnnotation ); + + return d->movie; +} + +EmbeddedFile* RichMediaAnnotation::embeddedFile() const +{ + Q_D( const RichMediaAnnotation ); + + return d->embeddedFile; +} + +void RichMediaAnnotation::setEmbeddedFile( EmbeddedFile *embeddedFile ) +{ + Q_D( RichMediaAnnotation ); + + delete d->embeddedFile; + d->embeddedFile = embeddedFile; +} diff --git a/core/annotations.h b/core/annotations.h index bbe2c5c40..5653097fe 100644 --- a/core/annotations.h +++ b/core/annotations.h @@ -45,6 +45,7 @@ class SoundAnnotationPrivate; class MovieAnnotationPrivate; class ScreenAnnotationPrivate; class WidgetAnnotationPrivate; +class RichMediaAnnotationPrivate; /** * @short Helper class for (recursive) annotation retrieval/storage. @@ -116,6 +117,7 @@ class OKULARCORE_EXPORT Annotation AMovie = 11, ///< A movie annotation AScreen = 12, ///< A screen annotation AWidget = 13, ///< A widget annotation + ARichMedia = 14,///< A rich media annotation A_BASE = 0 ///< The annotation base class }; @@ -1648,6 +1650,67 @@ class OKULARCORE_EXPORT WidgetAnnotation : public Annotation Q_DISABLE_COPY( WidgetAnnotation ) }; +/** + * \short RichMedia annotation. + * + * The rich media annotation represents an video or sound on a page. + * + * @since 1.0 + */ +class OKULARCORE_EXPORT RichMediaAnnotation : public Annotation +{ + public: + /** + * Creates a new rich media annotation. + */ + RichMediaAnnotation(); + + /** + * Creates a new rich media annotation from the xml @p description + */ + RichMediaAnnotation( const QDomNode &description ); + + /** + * Destroys the rich media annotation. + */ + virtual ~RichMediaAnnotation(); + + /** + * Returns the sub type of the rich media annotation. + */ + SubType subType() const; + + /** + * Stores the rich media annotation as xml in @p document + * under the given @p parentNode. + */ + void store( QDomNode &parentNode, QDomDocument &document ) const; + + /** + * Gets the movie object. + */ + Movie* movie() const; + + /** + * Sets the new @p movie object. + */ + void setMovie( Movie *movie ); + + /** + * Sets the @p object representing the embedded file. + */ + void setEmbeddedFile( EmbeddedFile *object ); + + /** + * Gets the embedded file object. + */ + EmbeddedFile* embeddedFile() const; + + private: + Q_DECLARE_PRIVATE( RichMediaAnnotation ) + Q_DISABLE_COPY( RichMediaAnnotation ) +}; + } #endif diff --git a/generators/poppler/annots.cpp b/generators/poppler/annots.cpp index 5aca423fa..9d320229d 100644 --- a/generators/poppler/annots.cpp +++ b/generators/poppler/annots.cpp @@ -28,6 +28,9 @@ Q_DECLARE_METATYPE( Poppler::Annotation* ) extern Okular::Sound* createSoundFromPopplerSound( const Poppler::SoundObject *popplerSound ); extern Okular::Movie* createMovieFromPopplerMovie( const Poppler::MovieObject *popplerMovie ); extern Okular::Movie* createMovieFromPopplerScreen( const Poppler::LinkRendition *popplerScreen ); +#ifdef HAVE_POPPLER_0_36 +extern QPair createMovieFromPopplerRichMedia( const Poppler::RichMediaAnnotation *popplerRichMedia ); +#endif static void disposeAnnotation( const Okular::Annotation *ann ) @@ -317,6 +320,25 @@ Okular::Annotation* createAnnotationFromPopplerAnnotation( Poppler::Annotation * *doDelete = false; break; } +#ifdef HAVE_POPPLER_0_36 + case Poppler::Annotation::ARichMedia: + { + Poppler::RichMediaAnnotation * richmediaann = static_cast< Poppler::RichMediaAnnotation * >( ann ); + const QPair result = createMovieFromPopplerRichMedia( richmediaann ); + + if ( result.first ) { + Okular::RichMediaAnnotation * r = new Okular::RichMediaAnnotation(); + tieToOkularAnn = true; + *doDelete = false; + annotation = r; + + r->setMovie( result.first ); + r->setEmbeddedFile( result.second ); + } + + break; + } +#endif case Poppler::Annotation::AText: case Poppler::Annotation::ALine: case Poppler::Annotation::AGeom: diff --git a/generators/poppler/config-okular-poppler.h.cmake b/generators/poppler/config-okular-poppler.h.cmake index 17280e3ae..792dcf1fa 100644 --- a/generators/poppler/config-okular-poppler.h.cmake +++ b/generators/poppler/config-okular-poppler.h.cmake @@ -3,3 +3,6 @@ /* Defined if we have the 0.28 version of the Poppler library */ #cmakedefine HAVE_POPPLER_0_28 1 + +/* Defined if we have the 0.36 version of the Poppler library */ +#cmakedefine HAVE_POPPLER_0_36 1 diff --git a/generators/poppler/generator_pdf.cpp b/generators/poppler/generator_pdf.cpp index ae8599b8b..4c87c2d18 100644 --- a/generators/poppler/generator_pdf.cpp +++ b/generators/poppler/generator_pdf.cpp @@ -199,6 +199,102 @@ Okular::Movie* createMovieFromPopplerScreen( const Poppler::LinkRendition *poppl return movie; } +#ifdef HAVE_POPPLER_0_36 +QPair createMovieFromPopplerRichMedia( const Poppler::RichMediaAnnotation *popplerRichMedia ) +{ + const QPair emptyResult(0, 0); + + /** + * To convert a Flash/Video based RichMedia annotation to a movie, we search for the first + * Flash/Video richmedia instance and parse the flashVars parameter for the 'source' identifier. + * That identifier is then used to find the associated embedded file through the assets + * mapping. + */ + const Poppler::RichMediaAnnotation::Content *content = popplerRichMedia->content(); + if ( !content ) + return emptyResult; + + const QList configurations = content->configurations(); + if ( configurations.isEmpty() ) + return emptyResult; + + const Poppler::RichMediaAnnotation::Configuration *configuration = configurations[0]; + + const QList instances = configuration->instances(); + if ( instances.isEmpty() ) + return emptyResult; + + const Poppler::RichMediaAnnotation::Instance *instance = instances[0]; + + if ( ( instance->type() != Poppler::RichMediaAnnotation::Instance::TypeFlash ) && + ( instance->type() != Poppler::RichMediaAnnotation::Instance::TypeVideo ) ) + return emptyResult; + + const Poppler::RichMediaAnnotation::Params *params = instance->params(); + if ( !params ) + return emptyResult; + + QString sourceId; + bool playbackLoops = false; + + const QStringList flashVars = params->flashVars().split( QLatin1Char( '&' ) ); + foreach ( const QString & flashVar, flashVars ) { + const int pos = flashVar.indexOf( QLatin1Char( '=' ) ); + if ( pos == -1 ) + continue; + + const QString key = flashVar.left( pos ); + const QString value = flashVar.mid( pos + 1 ); + + if ( key == QLatin1String( "source" ) ) + sourceId = value; + else if ( key == QLatin1String( "loop" ) ) + playbackLoops = ( value == QLatin1String( "true" ) ? true : false ); + } + + if ( sourceId.isEmpty() ) + return emptyResult; + + const QList assets = content->assets(); + if ( assets.isEmpty() ) + return emptyResult; + + Poppler::RichMediaAnnotation::Asset *matchingAsset = 0; + foreach ( Poppler::RichMediaAnnotation::Asset *asset, assets ) { + if ( asset->name() == sourceId ) { + matchingAsset = asset; + break; + } + } + + if ( !matchingAsset ) + return emptyResult; + + Poppler::EmbeddedFile *embeddedFile = matchingAsset->embeddedFile(); + if ( !embeddedFile ) + return emptyResult; + + Okular::EmbeddedFile *pdfEmbeddedFile = new PDFEmbeddedFile( embeddedFile ); + + Okular::Movie *movie = new Okular::Movie( embeddedFile->name(), embeddedFile->data() ); + movie->setPlayMode( playbackLoops ? Okular::Movie::PlayRepeat : Okular::Movie::PlayOnce ); + + if ( popplerRichMedia && popplerRichMedia->settings() && popplerRichMedia->settings()->activation() ) { + if ( popplerRichMedia->settings()->activation()->condition() == Poppler::RichMediaAnnotation::Activation::PageOpened || + popplerRichMedia->settings()->activation()->condition() == Poppler::RichMediaAnnotation::Activation::PageVisible ) { + movie->setAutoPlay( true ); + } else { + movie->setAutoPlay( false ); + } + + } else { + movie->setAutoPlay( false ); + } + + return qMakePair(movie, pdfEmbeddedFile); +} +#endif + /** * Note: the function will take ownership of the popplerLink object. */ diff --git a/ui/annotationpopup.cpp b/ui/annotationpopup.cpp index 65fa598e2..da42b6acb 100644 --- a/ui/annotationpopup.cpp +++ b/ui/annotationpopup.cpp @@ -21,6 +21,33 @@ Q_DECLARE_METATYPE( AnnotationPopup::AnnotPagePair ) +namespace { + +bool annotationHasFileAttachment( Okular::Annotation *annotation ) +{ + return ( annotation->subType() == Okular::Annotation::AFileAttachment || annotation->subType() == Okular::Annotation::ARichMedia ); +} + +Okular::EmbeddedFile* embeddedFileFromAnnotation( Okular::Annotation *annotation ) +{ + if ( annotation->subType() == Okular::Annotation::AFileAttachment ) + { + const Okular::FileAttachmentAnnotation *fileAttachAnnot = static_cast( annotation ); + return fileAttachAnnot->embeddedFile(); + } + else if ( annotation->subType() == Okular::Annotation::ARichMedia ) + { + const Okular::RichMediaAnnotation *richMediaAnnot = static_cast( annotation ); + return richMediaAnnot->embeddedFile(); + } + else + { + return 0; + } +} + +} + AnnotationPopup::AnnotationPopup( Okular::Document *document, MenuMode mode, QWidget *parent ) : mParent( parent ), mDocument( document ), mMenuMode( mode ) @@ -42,7 +69,6 @@ void AnnotationPopup::exec( const QPoint &point ) QMenu menu( mParent ); QAction *action = 0; - Okular::FileAttachmentAnnotation *fileAttachAnnot = 0; const char *actionTypeId = "actionType"; @@ -80,15 +106,18 @@ void AnnotationPopup::exec( const QPoint &point ) action->setEnabled( onlyOne ); action->setProperty( actionTypeId, propertiesId ); - if ( onlyOne && pair.annotation->subType() == Okular::Annotation::AFileAttachment ) + if ( onlyOne && annotationHasFileAttachment( pair.annotation ) ) { - menu.addSeparator(); - fileAttachAnnot = static_cast< Okular::FileAttachmentAnnotation * >( pair.annotation ); - const QString saveText = i18nc( "%1 is the name of the file to save", "&Save '%1'...", fileAttachAnnot->embeddedFile()->name() ); + const Okular::EmbeddedFile *embeddedFile = embeddedFileFromAnnotation( pair.annotation ); + if ( embeddedFile ) + { + const QString saveText = i18nc( "%1 is the name of the file to save", "&Save '%1'...", embeddedFile->name() ); - action = menu.addAction( QIcon::fromTheme( "document-save" ), saveText ); - action->setData( QVariant::fromValue( pair ) ); - action->setProperty( actionTypeId, saveId ); + menu.addSeparator(); + action = menu.addAction( QIcon::fromTheme( "document-save" ), saveText ); + action->setData( QVariant::fromValue( pair ) ); + action->setProperty( actionTypeId, saveId ); + } } } else @@ -111,15 +140,18 @@ void AnnotationPopup::exec( const QPoint &point ) action->setData( QVariant::fromValue( pair ) ); action->setProperty( actionTypeId, propertiesId ); - if ( pair.annotation->subType() == Okular::Annotation::AFileAttachment ) + if ( annotationHasFileAttachment( pair.annotation ) ) { - menu.addSeparator(); - fileAttachAnnot = static_cast< Okular::FileAttachmentAnnotation * >( pair.annotation ); - const QString saveText = i18nc( "%1 is the name of the file to save", "&Save '%1'...", fileAttachAnnot->embeddedFile()->name() ); - - action = menu.addAction( QIcon::fromTheme( "document-save" ), saveText ); - action->setData( QVariant::fromValue( pair ) ); - action->setProperty( actionTypeId, saveId ); + const Okular::EmbeddedFile *embeddedFile = embeddedFileFromAnnotation( pair.annotation ); + if ( embeddedFile ) + { + const QString saveText = i18nc( "%1 is the name of the file to save", "&Save '%1'...", embeddedFile->name() ); + + menu.addSeparator(); + action = menu.addAction( QIcon::fromTheme( "document-save" ), saveText ); + action->setData( QVariant::fromValue( pair ) ); + action->setProperty( actionTypeId, saveId ); + } } } } @@ -148,8 +180,8 @@ void AnnotationPopup::exec( const QPoint &point ) propdialog.exec(); } } else if( actionType == saveId ) { - const Okular::FileAttachmentAnnotation * fileAttachAnnot = static_cast< Okular::FileAttachmentAnnotation * >( pair.annotation ); - GuiUtils::saveEmbeddedFile( fileAttachAnnot->embeddedFile(), mParent ); + Okular::EmbeddedFile *embeddedFile = embeddedFileFromAnnotation( pair.annotation ); + GuiUtils::saveEmbeddedFile( embeddedFile, mParent ); } } } diff --git a/ui/guiutils.cpp b/ui/guiutils.cpp index 86d01eb81..3d7f1d62e 100644 --- a/ui/guiutils.cpp +++ b/ui/guiutils.cpp @@ -122,6 +122,9 @@ QString captionForAnnotation( const Okular::Annotation * ann ) case Okular::Annotation::AWidget: ret = i18nc( "Caption for a widget annotation", "Widget" ); break; + case Okular::Annotation::ARichMedia: + ret = i18nc( "Caption for a rich media annotation", "Rich Media" ); + break; case Okular::Annotation::A_BASE: break; } diff --git a/ui/pageview.cpp b/ui/pageview.cpp index ce7564479..452869169 100644 --- a/ui/pageview.cpp +++ b/ui/pageview.cpp @@ -973,6 +973,13 @@ void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setup item->videoWidgets().insert( movieAnn->movie(), vw ); vw->pageInitialized(); } + else if ( a->subType() == Okular::Annotation::ARichMedia ) + { + Okular::RichMediaAnnotation * richMediaAnn = static_cast< Okular::RichMediaAnnotation * >( a ); + VideoWidget * vw = new VideoWidget( richMediaAnn, richMediaAnn->movie(), d->document, viewport() ); + item->videoWidgets().insert( richMediaAnn->movie(), vw ); + vw->pageInitialized(); + } else if ( a->subType() == Okular::Annotation::AScreen ) { const Okular::ScreenAnnotation * screenAnn = static_cast< Okular::ScreenAnnotation * >( a ); @@ -2426,6 +2433,12 @@ void PageView::mouseReleaseEvent( QMouseEvent * e ) vw->show(); vw->play(); } + else if ( ann->subType() == Okular::Annotation::ARichMedia ) + { + VideoWidget *vw = pageItem->videoWidgets().value( static_cast( ann )->movie() ); + vw->show(); + vw->play(); + } else if ( ann->subType() == Okular::Annotation::AScreen ) { d->document->processAction( static_cast( ann )->action() ); @@ -3914,6 +3927,11 @@ void PageView::updateCursor( const QPoint &p ) d->mouseOnRect = true; setCursor( Qt::PointingHandCursor ); } + else if ( annotation->subType() == Okular::Annotation::ARichMedia ) + { + d->mouseOnRect = true; + setCursor( Qt::PointingHandCursor ); + } else if ( annotation->subType() == Okular::Annotation::AScreen ) { if ( GuiUtils::renditionMovieFromScreenAnnotation( static_cast< const Okular::ScreenAnnotation * >( annotation ) ) != 0 ) diff --git a/ui/presentationwidget.cpp b/ui/presentationwidget.cpp index 4b90849ab..76ffe8242 100644 --- a/ui/presentationwidget.cpp +++ b/ui/presentationwidget.cpp @@ -335,6 +335,15 @@ void PresentationWidget::notifySetup( const QVector< Okular::Page * > & pageSet, frame->videoWidgets.insert( movieAnn->movie(), vw ); vw->pageInitialized(); } + else if ( a->subType() == Okular::Annotation::ARichMedia ) + { + Okular::RichMediaAnnotation * richMediaAnn = static_cast< Okular::RichMediaAnnotation * >( a ); + if ( richMediaAnn->movie() ) { + VideoWidget * vw = new VideoWidget( richMediaAnn, richMediaAnn->movie(), m_document, this ); + frame->videoWidgets.insert( richMediaAnn->movie(), vw ); + vw->pageInitialized(); + } + } else if ( a->subType() == Okular::Annotation::AScreen ) { const Okular::ScreenAnnotation * screenAnn = static_cast< Okular::ScreenAnnotation * >( a ); @@ -629,6 +638,15 @@ void PresentationWidget::mousePressEvent( QMouseEvent * e ) vw->play(); return; } + else if ( annotation->subType() == Okular::Annotation::ARichMedia ) + { + const Okular::RichMediaAnnotation *richMediaAnnotation = static_cast( annotation ); + + VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( richMediaAnnotation->movie() ); + vw->show(); + vw->play(); + return; + } else if ( annotation->subType() == Okular::Annotation::AScreen ) { m_document->processAction( static_cast( annotation )->action() ); @@ -893,6 +911,7 @@ void PresentationWidget::testCursorOnLink( int x, int y ) const bool needsHandCursor = ( ( link != 0 ) || ( ( annotation != 0 ) && ( annotation->subType() == Okular::Annotation::AMovie ) ) || + ( ( annotation != 0 ) && ( annotation->subType() == Okular::Annotation::ARichMedia ) ) || ( ( annotation != 0 ) && ( annotation->subType() == Okular::Annotation::AScreen ) && ( GuiUtils::renditionMovieFromScreenAnnotation( static_cast< const Okular::ScreenAnnotation * >( annotation ) ) != 0 ) ) ); // only react on changes (in/out from a link)