okular/generators/poppler/annots.cpp

432 lines
17 KiB
C++
Raw Normal View History

/***************************************************************************
* Copyright (C) 2008 by Pino Toscano <pino@kde.org> *
* Copyright (C) 2012 by Guillermo A. Amaral B. <gamaral@kde.org> *
* Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group *
* company, info@kdab.com. Work sponsored by the *
* LiMux project of the city of Munich *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
2018-08-31 09:23:45 +00:00
#include "annots.h"
#include <poppler-annotation.h>
// qt/kde includes
2018-08-31 09:23:45 +00:00
#include <QLoggingCategory>
#include <QVariant>
#include <core/annotations.h>
#include <core/area.h>
2014-09-11 19:12:27 +00:00
#include "debug_pdf.h"
#include "generator_pdf.h"
#include "popplerembeddedfile.h"
#include "config-okular-poppler.h"
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 )
{
Poppler::Annotation *popplerAnn = qvariant_cast< Poppler::Annotation * >( ann->nativeId() );
delete popplerAnn;
}
static QPointF normPointToPointF( const Okular::NormalizedPoint& pt )
{
return QPointF(pt.x, pt.y);
}
static QRectF normRectToRectF( const Okular::NormalizedRect& rect )
{
return QRectF( QPointF(rect.left, rect.top), QPointF(rect.right, rect.bottom) );
}
// Poppler and Okular share the same flag values, but we don't want to export internal flags
static int maskExportedFlags(int flags)
{
return flags & ( Okular::Annotation::Hidden |
Okular::Annotation::FixedSize |
Okular::Annotation::FixedRotation |
Okular::Annotation::DenyPrint |
Okular::Annotation::DenyWrite |
Okular::Annotation::DenyDelete |
Okular::Annotation::ToggleHidingOnMouse );
}
//BEGIN PopplerAnnotationProxy implementation
PopplerAnnotationProxy::PopplerAnnotationProxy( Poppler::Document *doc, QMutex *userMutex, QHash<Okular::Annotation*, Poppler::Annotation*> *annotsOnOpenHash )
: ppl_doc ( doc ), mutex ( userMutex ), annotationsOnOpenHash( annotsOnOpenHash )
{
}
PopplerAnnotationProxy::~PopplerAnnotationProxy()
{
}
bool PopplerAnnotationProxy::supports( Capability cap ) const
{
switch ( cap )
{
case Addition:
case Modification:
case Removal:
return true;
default:
return false;
}
}
void PopplerAnnotationProxy::notifyAddition( Okular::Annotation *okl_ann, int page )
{
// Export annotation to DOM
QDomDocument doc;
2015-10-29 12:37:11 +00:00
QDomElement dom_ann = doc.createElement( QStringLiteral("root") );
Okular::AnnotationUtils::storeAnnotation( okl_ann, dom_ann, doc );
QMutexLocker ml(mutex);
// Create poppler annotation
Poppler::Annotation *ppl_ann = Poppler::AnnotationUtils::createAnnotation( dom_ann );
// Poppler doesn't render StampAnnotations yet
if ( ppl_ann->subType() != Poppler::Annotation::AStamp )
okl_ann->setFlags( okl_ann->flags() | Okular::Annotation::ExternallyDrawn );
// Poppler stores highlight points in swapped order
if ( ppl_ann->subType() == Poppler::Annotation::AHighlight )
{
Poppler::HighlightAnnotation * hlann = static_cast<Poppler::HighlightAnnotation*>( ppl_ann );
QList<Poppler::HighlightAnnotation::Quad> quads = hlann->highlightQuads();
QMutableListIterator<Poppler::HighlightAnnotation::Quad> it( quads );
while ( it.hasNext() )
{
Poppler::HighlightAnnotation::Quad &q = it.next();
QPointF t;
t = q.points[3];
q.points[3] = q.points[0];
q.points[0] = t;
t = q.points[2];
q.points[2] = q.points[1];
q.points[1] = t;
}
hlann->setHighlightQuads( quads );
}
// Bind poppler object to page
Poppler::Page *ppl_page = ppl_doc->page( page );
ppl_page->addAnnotation( ppl_ann );
delete ppl_page;
// Set pointer to poppler annotation as native Id
okl_ann->setNativeId( qVariantFromValue( ppl_ann ) );
okl_ann->setDisposeDataFunction( disposeAnnotation );
2014-09-11 19:12:27 +00:00
qCDebug(OkularPdfDebug) << okl_ann->uniqueName();
}
void PopplerAnnotationProxy::notifyModification( const Okular::Annotation *okl_ann, int page, bool appearanceChanged )
{
Q_UNUSED( page );
Q_UNUSED( appearanceChanged );
Poppler::Annotation *ppl_ann = qvariant_cast<Poppler::Annotation*>( okl_ann->nativeId() );
if ( !ppl_ann ) // Ignore non-native annotations
return;
QMutexLocker ml(mutex);
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
if ( okl_ann->flags() & (Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized) )
{
// Okular ui already renders the annotation on its own
ppl_ann->setFlags( Poppler::Annotation::Hidden );
return;
}
// Set basic properties
// Note: flags and boundary must be set first in order to correctly handle
// FixedRotation annotations.
ppl_ann->setFlags(maskExportedFlags( okl_ann->flags() ));
ppl_ann->setBoundary(normRectToRectF( okl_ann->boundingRectangle() ));
ppl_ann->setAuthor( okl_ann->author() );
ppl_ann->setContents( okl_ann->contents() );
// Set style
Poppler::Annotation::Style s;
s.setColor( okl_ann->style().color() );
s.setWidth( okl_ann->style().width() );
s.setOpacity( okl_ann->style().opacity() );
ppl_ann->setStyle( s );
// Set type-specific properties (if any)
switch ( ppl_ann->subType() )
{
case Poppler::Annotation::AText:
{
const Okular::TextAnnotation * okl_txtann = static_cast<const Okular::TextAnnotation*>(okl_ann);
Poppler::TextAnnotation * ppl_txtann = static_cast<Poppler::TextAnnotation*>(ppl_ann);
ppl_txtann->setTextIcon( okl_txtann->textIcon() );
ppl_txtann->setTextFont( okl_txtann->textFont() );
#ifdef HAVE_POPPLER_0_69
ppl_txtann->setTextColor( okl_txtann->textColor() );
#endif //HAVE_POPPLER_0_69
ppl_txtann->setInplaceAlign( okl_txtann->inplaceAlignment() );
ppl_txtann->setCalloutPoints( QVector<QPointF>() );
ppl_txtann->setInplaceIntent( (Poppler::TextAnnotation::InplaceIntent)okl_txtann->inplaceIntent() );
break;
}
case Poppler::Annotation::ALine:
{
const Okular::LineAnnotation * okl_lineann = static_cast<const Okular::LineAnnotation*>(okl_ann);
Poppler::LineAnnotation * ppl_lineann = static_cast<Poppler::LineAnnotation*>(ppl_ann);
QLinkedList<QPointF> points;
foreach ( const Okular::NormalizedPoint &p, okl_lineann->linePoints() )
points.append(normPointToPointF( p ));
ppl_lineann->setLinePoints( points );
ppl_lineann->setLineStartStyle( (Poppler::LineAnnotation::TermStyle)okl_lineann->lineStartStyle() );
ppl_lineann->setLineEndStyle( (Poppler::LineAnnotation::TermStyle)okl_lineann->lineEndStyle() );
ppl_lineann->setLineClosed( okl_lineann->lineClosed() );
ppl_lineann->setLineInnerColor( okl_lineann->lineInnerColor() );
ppl_lineann->setLineLeadingForwardPoint( okl_lineann->lineLeadingForwardPoint() );
ppl_lineann->setLineLeadingBackPoint( okl_lineann->lineLeadingBackwardPoint() );
ppl_lineann->setLineShowCaption( okl_lineann->showCaption() );
ppl_lineann->setLineIntent( (Poppler::LineAnnotation::LineIntent)okl_lineann->lineIntent() );
break;
}
case Poppler::Annotation::AGeom:
{
const Okular::GeomAnnotation * okl_geomann = static_cast<const Okular::GeomAnnotation*>(okl_ann);
Poppler::GeomAnnotation * ppl_geomann = static_cast<Poppler::GeomAnnotation*>(ppl_ann);
ppl_geomann->setGeomType( (Poppler::GeomAnnotation::GeomType)okl_geomann->geometricalType() );
ppl_geomann->setGeomInnerColor( okl_geomann->geometricalInnerColor() );
break;
}
case Poppler::Annotation::AHighlight:
{
const Okular::HighlightAnnotation * okl_hlann = static_cast<const Okular::HighlightAnnotation*>(okl_ann);
Poppler::HighlightAnnotation * ppl_hlann = static_cast<Poppler::HighlightAnnotation*>(ppl_ann);
ppl_hlann->setHighlightType( (Poppler::HighlightAnnotation::HighlightType)okl_hlann->highlightType() );
break;
}
case Poppler::Annotation::AStamp:
{
const Okular::StampAnnotation * okl_stampann = static_cast<const Okular::StampAnnotation*>(okl_ann);
Poppler::StampAnnotation * ppl_stampann = static_cast<Poppler::StampAnnotation*>(ppl_ann);
ppl_stampann->setStampIconName( okl_stampann->stampIconName() );
break;
}
case Poppler::Annotation::AInk:
{
const Okular::InkAnnotation * okl_inkann = static_cast<const Okular::InkAnnotation*>(okl_ann);
Poppler::InkAnnotation * ppl_inkann = static_cast<Poppler::InkAnnotation*>(ppl_ann);
QList< QLinkedList<QPointF> > paths;
foreach ( const QLinkedList<Okular::NormalizedPoint> &path, okl_inkann->inkPaths() )
{
QLinkedList<QPointF> points;
foreach ( const Okular::NormalizedPoint &p, path )
points.append(normPointToPointF( p ));
paths.append( points );
}
ppl_inkann->setInkPaths( paths );
break;
}
default:
2014-09-11 19:12:27 +00:00
qCDebug(OkularPdfDebug) << "Type-specific property modification is not implemented for this annotation type";
break;
}
2014-09-11 19:12:27 +00:00
qCDebug(OkularPdfDebug) << okl_ann->uniqueName();
}
void PopplerAnnotationProxy::notifyRemoval( Okular::Annotation *okl_ann, int page )
{
Poppler::Annotation *ppl_ann = qvariant_cast<Poppler::Annotation*>( okl_ann->nativeId() );
if ( !ppl_ann ) // Ignore non-native annotations
return;
QMutexLocker ml(mutex);
Poppler::Page *ppl_page = ppl_doc->page( page );
annotationsOnOpenHash->remove( okl_ann );
ppl_page->removeAnnotation( ppl_ann ); // Also destroys ppl_ann
delete ppl_page;
okl_ann->setNativeId( qVariantFromValue(0) ); // So that we don't double-free in disposeAnnotation
2014-09-11 19:12:27 +00:00
qCDebug(OkularPdfDebug) << okl_ann->uniqueName();
}
//END PopplerAnnotationProxy implementation
Okular::Annotation* createAnnotationFromPopplerAnnotation( Poppler::Annotation *ann, bool *doDelete )
{
Okular::Annotation *annotation = 0;
*doDelete = true;
bool tieToOkularAnn = false;
bool externallyDrawn = false;
switch ( ann->subType() )
{
case Poppler::Annotation::AFileAttachment:
{
Poppler::FileAttachmentAnnotation * attachann = static_cast< Poppler::FileAttachmentAnnotation * >( ann );
Okular::FileAttachmentAnnotation * f = new Okular::FileAttachmentAnnotation();
annotation = f;
tieToOkularAnn = true;
*doDelete = false;
f->setFileIconName( attachann->fileIconName() );
f->setEmbeddedFile( new PDFEmbeddedFile( attachann->embeddedFile() ) );
break;
}
case Poppler::Annotation::ASound:
{
Poppler::SoundAnnotation * soundann = static_cast< Poppler::SoundAnnotation * >( ann );
Okular::SoundAnnotation * s = new Okular::SoundAnnotation();
annotation = s;
s->setSoundIconName( soundann->soundIconName() );
s->setSound( createSoundFromPopplerSound( soundann->sound() ) );
break;
}
case Poppler::Annotation::AMovie:
{
Poppler::MovieAnnotation * movieann = static_cast< Poppler::MovieAnnotation * >( ann );
Okular::MovieAnnotation * m = new Okular::MovieAnnotation();
annotation = m;
tieToOkularAnn = true;
*doDelete = false;
m->setMovie( createMovieFromPopplerMovie( movieann->movie() ) );
break;
}
case Poppler::Annotation::AWidget:
{
annotation = new Okular::WidgetAnnotation();
break;
}
case Poppler::Annotation::AScreen:
{
Okular::ScreenAnnotation * m = new Okular::ScreenAnnotation();
annotation = m;
tieToOkularAnn = true;
*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:
case Poppler::Annotation::AHighlight:
case Poppler::Annotation::AInk:
case Poppler::Annotation::ACaret:
externallyDrawn = true;
/* fallthrough */
case Poppler::Annotation::AStamp:
tieToOkularAnn = true;
*doDelete = false;
/* fallthrough */
default:
{
// this is uber ugly but i don't know a better way to do it without introducing a poppler::annotation dependency on core
QDomDocument doc;
2015-10-29 12:37:11 +00:00
QDomElement root = doc.createElement( QStringLiteral("root") );
doc.appendChild( root );
Poppler::AnnotationUtils::storeAnnotation( ann, root, doc );
annotation = Okular::AnnotationUtils::createAnnotation( root );
break;
}
}
if ( annotation )
{
// the Contents field might have lines separated by \r
QString contents = ann->contents();
contents.replace( QLatin1Char( '\r' ), QLatin1Char( '\n' ) );
annotation->setAuthor( ann->author() );
annotation->setContents( contents );
annotation->setUniqueName( ann->uniqueName() );
annotation->setModificationDate( ann->modificationDate() );
annotation->setCreationDate( ann->creationDate() );
annotation->setFlags( ann->flags() | Okular::Annotation::External );
annotation->setBoundingRectangle( Okular::NormalizedRect::fromQRectF( ann->boundary() ) );
if (externallyDrawn)
annotation->setFlags( annotation->flags() | Okular::Annotation::ExternallyDrawn );
// Poppler stores highlight points in swapped order
if ( annotation->subType() == Okular::Annotation::AHighlight )
{
Okular::HighlightAnnotation * hlann = static_cast<Okular::HighlightAnnotation*>( annotation );
QList<Okular::HighlightAnnotation::Quad> &quads = hlann->highlightQuads();
for (QList<Okular::HighlightAnnotation::Quad>::iterator it = quads.begin(); it != quads.end(); ++it)
{
Okular::NormalizedPoint t;
t = it->point( 3 );
it->setPoint( it->point(0), 3 );
it->setPoint( t, 0 );
t = it->point( 2 );
it->setPoint( it->point(1), 2 );
it->setPoint( t, 1 );
}
}
if ( annotation->subType() == Okular::Annotation::AText )
{
Okular::TextAnnotation * txtann = static_cast<Okular::TextAnnotation*>( annotation );
if ( txtann->textType() == Okular::TextAnnotation::Linked )
{
Poppler::TextAnnotation * ppl_txtann = static_cast<Poppler::TextAnnotation*>( ann );
// Poppler and Okular assume a different default icon name in XML
// We re-read it via getter, which always tells the right one
txtann->setTextIcon( ppl_txtann->textIcon() );
}
}
// TODO clone style
// TODO clone window
// TODO clone revisions
if ( tieToOkularAnn )
{
annotation->setNativeId( qVariantFromValue( ann ) );
annotation->setDisposeDataFunction( disposeAnnotation );
}
}
return annotation;
}