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
frameworks
Tobias Koenig 11 years ago
parent e608908961
commit 8b603c174d
  1. 12
      cmake/modules/FindPoppler.cmake
  2. 108
      core/annotations.cpp
  3. 63
      core/annotations.h
  4. 22
      generators/poppler/annots.cpp
  5. 3
      generators/poppler/config-okular-poppler.h.cmake
  6. 96
      generators/poppler/generator_pdf.cpp
  7. 68
      ui/annotationpopup.cpp
  8. 3
      ui/guiutils.cpp
  9. 18
      ui/pageview.cpp
  10. 19
      ui/presentationwidget.cpp

@ -79,12 +79,24 @@ int main()
}
" HAVE_POPPLER_0_28)
check_cxx_source_compiles("
#include <poppler-qt5.h>
int main()
{
Poppler::Page *p = 0;
p->annotations( QSet<Poppler::Annotation::SubType>() << 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})")

@ -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;
}

@ -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

@ -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<Okular::Movie*, Okular::EmbeddedFile*> 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<Okular::Movie*, Okular::EmbeddedFile*> 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:

@ -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

@ -199,6 +199,102 @@ Okular::Movie* createMovieFromPopplerScreen( const Poppler::LinkRendition *poppl
return movie;
}
#ifdef HAVE_POPPLER_0_36
QPair<Okular::Movie*, Okular::EmbeddedFile*> createMovieFromPopplerRichMedia( const Poppler::RichMediaAnnotation *popplerRichMedia )
{
const QPair<Okular::Movie*, Okular::EmbeddedFile*> 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<Poppler::RichMediaAnnotation::Configuration*> configurations = content->configurations();
if ( configurations.isEmpty() )
return emptyResult;
const Poppler::RichMediaAnnotation::Configuration *configuration = configurations[0];
const QList<Poppler::RichMediaAnnotation::Instance*> 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<Poppler::RichMediaAnnotation::Asset*> 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.
*/

@ -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<Okular::FileAttachmentAnnotation*>( annotation );
return fileAttachAnnot->embeddedFile();
}
else if ( annotation->subType() == Okular::Annotation::ARichMedia )
{
const Okular::RichMediaAnnotation *richMediaAnnot = static_cast<Okular::RichMediaAnnotation*>( 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 );
}
}
}

@ -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;
}

@ -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<Okular::RichMediaAnnotation*>( ann )->movie() );
vw->show();
vw->play();
}
else if ( ann->subType() == Okular::Annotation::AScreen )
{
d->document->processAction( static_cast<Okular::ScreenAnnotation*>( 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 )

@ -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<const Okular::RichMediaAnnotation*>( 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<const Okular::ScreenAnnotation*>( 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)

Loading…
Cancel
Save