blob: 5f23782dc5199097279527d9fe23055129549d66 [file] [log] [blame]
/* poppler-annotation.cc: qt interface to poppler
* Copyright (C) 2006, 2009, 2012-2015, 2018, 2019 Albert Astals Cid <aacid@kde.org>
* Copyright (C) 2006, 2008, 2010 Pino Toscano <pino@kde.org>
* Copyright (C) 2012, Guillermo A. Amaral B. <gamaral@kde.org>
* Copyright (C) 2012-2014 Fabio D'Urso <fabiodurso@hotmail.it>
* Copyright (C) 2012, 2015, Tobias Koenig <tobias.koenig@kdab.com>
* Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
* Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
* Copyright (C) 2018 Intevation GmbH <intevation@intevation.de>
* Copyright (C) 2018 Dileep Sankhla <sankhla.dileep96@gmail.com>
* Copyright (C) 2018, 2019 Tobias Deiminger <haxtibal@posteo.de>
* Copyright (C) 2018 Carlos Garcia Campos <carlosgc@gnome.org>
* Adapting code from
* Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>
*
* 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, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
// qt/kde includes
#include <QtCore/QRegExp>
#include <QtCore/QtAlgorithms>
#include <QtXml/QDomElement>
#include <QtGui/QColor>
#include <QtGui/QTransform>
// local includes
#include "poppler-annotation.h"
#include "poppler-link.h"
#include "poppler-qt5.h"
#include "poppler-annotation-helper.h"
#include "poppler-annotation-private.h"
#include "poppler-page-private.h"
#include "poppler-private.h"
// poppler includes
#include <Page.h>
#include <Annot.h>
#include <Gfx.h>
#include <Error.h>
#include <FileSpec.h>
#include <Link.h>
/* Almost all getters directly query the underlying poppler annotation, with
* the exceptions of link, file attachment, sound, movie and screen annotations,
* Whose data retrieval logic has not been moved yet. Their getters return
* static data set at creation time by findAnnotations
*/
namespace Poppler {
//BEGIN AnnotationUtils implementation
Annotation * AnnotationUtils::createAnnotation( const QDomElement & annElement )
{
// safety check on annotation element
if ( !annElement.hasAttribute( QStringLiteral("type") ) )
return nullptr;
// build annotation of given type
Annotation * annotation = nullptr;
int typeNumber = annElement.attribute( QStringLiteral("type") ).toInt();
switch ( typeNumber )
{
case Annotation::AText:
annotation = new TextAnnotation( annElement );
break;
case Annotation::ALine:
annotation = new LineAnnotation( annElement );
break;
case Annotation::AGeom:
annotation = new GeomAnnotation( annElement );
break;
case Annotation::AHighlight:
annotation = new HighlightAnnotation( annElement );
break;
case Annotation::AStamp:
annotation = new StampAnnotation( annElement );
break;
case Annotation::AInk:
annotation = new InkAnnotation( annElement );
break;
case Annotation::ACaret:
annotation = new CaretAnnotation( annElement );
break;
}
// return created annotation
return annotation;
}
void AnnotationUtils::storeAnnotation( const Annotation * ann, QDomElement & annElement,
QDomDocument & document )
{
// save annotation's type as element's attribute
annElement.setAttribute( QStringLiteral("type"), (uint)ann->subType() );
// append all annotation data as children of this node
ann->store( annElement, document );
}
QDomElement AnnotationUtils::findChildElement( const QDomNode & parentNode,
const QString & name )
{
// loop through the whole children and return a 'name' named element
QDomNode subNode = parentNode.firstChild();
while( subNode.isElement() )
{
QDomElement element = subNode.toElement();
if ( element.tagName() == name )
return element;
subNode = subNode.nextSibling();
}
// if the name can't be found, return a dummy null element
return QDomElement();
}
//END AnnotationUtils implementation
//BEGIN Annotation implementation
AnnotationPrivate::AnnotationPrivate()
: flags( 0 ), revisionScope ( Annotation::Root ),
revisionType ( Annotation::None ), pdfAnnot ( nullptr ), pdfPage ( nullptr ),
parentDoc ( nullptr )
{
}
void AnnotationPrivate::addRevision( Annotation *ann, Annotation::RevScope scope, Annotation::RevType type )
{
/* Since ownership stays with the caller, create an alias of ann */
revisions.append( ann->d_ptr->makeAlias() );
/* Set revision properties */
revisionScope = scope;
revisionType = type;
}
AnnotationPrivate::~AnnotationPrivate()
{
// Delete all children revisions
qDeleteAll( revisions );
// Release Annot object
if (pdfAnnot)
pdfAnnot->decRefCnt();
}
void AnnotationPrivate::tieToNativeAnnot(Annot *ann, ::Page *page, Poppler::DocumentData * doc)
{
if (pdfAnnot)
{
error(errIO, -1, "Annotation is already tied");
return;
}
pdfAnnot = ann;
pdfPage = page;
parentDoc = doc;
pdfAnnot->incRefCnt();
}
/* This method is called when a new annotation is created, after pdfAnnot and
* pdfPage have been set */
void AnnotationPrivate::flushBaseAnnotationProperties()
{
Q_ASSERT ( pdfPage );
Annotation *q = makeAlias(); // Setters are defined in the public class
// Since pdfAnnot has been set, this calls will write in the Annot object
q->setAuthor(author);
q->setContents(contents);
q->setUniqueName(uniqueName);
q->setModificationDate(modDate);
q->setCreationDate(creationDate);
q->setFlags(flags);
//q->setBoundary(boundary); -- already set by subclass-specific code
q->setStyle(style);
q->setPopup(popup);
// Flush revisions
foreach (Annotation *r, revisions)
{
// TODO: Flush revision
delete r; // Object is no longer needed
}
delete q;
// Clear some members to save memory
author.clear();
contents.clear();
uniqueName.clear();
revisions.clear();
}
// Returns matrix to convert from user space coords (oriented according to the
// specified rotation) to normalized coords
void AnnotationPrivate::fillNormalizationMTX(double MTX[6], int pageRotation) const
{
Q_ASSERT ( pdfPage );
// build a normalized transform matrix for this page at 100% scale
GfxState * gfxState = new GfxState( 72.0, 72.0, pdfPage->getCropBox(), pageRotation, true );
const double * gfxCTM = gfxState->getCTM();
double w = pdfPage->getCropWidth();
double h = pdfPage->getCropHeight();
// Swap width and height if the page is rotated landscape or seascape
if ( pageRotation == 90 || pageRotation == 270 )
{
double t = w;
w = h;
h = t;
}
for ( int i = 0; i < 6; i+=2 )
{
MTX[i] = gfxCTM[i] / w;
MTX[i+1] = gfxCTM[i+1] / h;
}
delete gfxState;
}
// Returns matrix to convert from user space coords (i.e. those that are stored
// in the PDF file) to normalized coords (i.e. those that we expose to clients).
// This method also applies a rotation around the top-left corner if the
// FixedRotation flag is set.
void AnnotationPrivate::fillTransformationMTX(double MTX[6]) const
{
Q_ASSERT ( pdfPage );
Q_ASSERT ( pdfAnnot );
const int pageRotate = pdfPage->getRotate();
if ( pageRotate == 0 || ( pdfAnnot->getFlags() & Annot::flagNoRotate ) == 0 )
{
// Use the normalization matrix for this page's rotation
fillNormalizationMTX( MTX, pageRotate );
}
else
{
// Clients expect coordinates relative to this page's rotation, but
// FixedRotation annotations internally use unrotated coordinates:
// construct matrix to both normalize and rotate coordinates using the
// top-left corner as rotation pivot
double MTXnorm[6];
fillNormalizationMTX( MTXnorm, pageRotate );
QTransform transform( MTXnorm[0], MTXnorm[1], MTXnorm[2],
MTXnorm[3], MTXnorm[4], MTXnorm[5] );
transform.translate( +pdfAnnot->getXMin(), +pdfAnnot->getYMax() );
transform.rotate( pageRotate );
transform.translate( -pdfAnnot->getXMin(), -pdfAnnot->getYMax() );
MTX[0] = transform.m11();
MTX[1] = transform.m12();
MTX[2] = transform.m21();
MTX[3] = transform.m22();
MTX[4] = transform.dx();
MTX[5] = transform.dy();
}
}
QRectF AnnotationPrivate::fromPdfRectangle(const PDFRectangle &r) const
{
double swp, MTX[6];
fillTransformationMTX(MTX);
QPointF p1, p2;
XPDFReader::transform( MTX, r.x1, r.y1, p1 );
XPDFReader::transform( MTX, r.x2, r.y2, p2 );
double tl_x = p1.x();
double tl_y = p1.y();
double br_x = p2.x();
double br_y = p2.y();
if (tl_x > br_x)
{
swp = tl_x;
tl_x = br_x;
br_x = swp;
}
if (tl_y > br_y)
{
swp = tl_y;
tl_y = br_y;
br_y = swp;
}
return QRectF( QPointF(tl_x,tl_y) , QPointF(br_x,br_y) );
}
// This function converts a boundary QRectF in normalized coords to a
// PDFRectangle in user coords. If the FixedRotation flag is set, this function
// also applies a rotation around the top-left corner: it's the inverse of
// the transformation produced by fillTransformationMTX, but we can't use
// fillTransformationMTX here because it relies on the native annotation
// object's boundary rect to be already set up.
PDFRectangle AnnotationPrivate::boundaryToPdfRectangle(const QRectF &r, int rFlags) const
{
Q_ASSERT ( pdfPage );
const int pageRotate = pdfPage->getRotate();
double MTX[6];
fillNormalizationMTX( MTX, pageRotate );
double tl_x, tl_y, br_x, br_y, swp;
XPDFReader::invTransform( MTX, r.topLeft(), tl_x, tl_y );
XPDFReader::invTransform( MTX, r.bottomRight(), br_x, br_y );
if (tl_x > br_x)
{
swp = tl_x;
tl_x = br_x;
br_x = swp;
}
if (tl_y > br_y)
{
swp = tl_y;
tl_y = br_y;
br_y = swp;
}
const int rotationFixUp = ( rFlags & Annotation::FixedRotation ) ? pageRotate : 0;
const double width = br_x - tl_x;
const double height = br_y - tl_y;
if ( rotationFixUp == 0 )
return PDFRectangle(tl_x, tl_y, br_x, br_y);
else if ( rotationFixUp == 90 )
return PDFRectangle(tl_x, tl_y - width, tl_x + height, tl_y);
else if ( rotationFixUp == 180 )
return PDFRectangle(br_x, tl_y - height, br_x + width, tl_y);
else // rotationFixUp == 270
return PDFRectangle(br_x, br_y - width, br_x + height, br_y);
}
AnnotPath * AnnotationPrivate::toAnnotPath(const QLinkedList<QPointF> &list) const
{
const int count = list.size();
std::vector<AnnotCoord> ac;
ac.reserve(count);
double MTX[6];
fillTransformationMTX(MTX);
foreach (const QPointF &p, list)
{
double x, y;
XPDFReader::invTransform( MTX, p, x, y );
ac.emplace_back(x, y);
}
return new AnnotPath(std::move(ac));
}
QList<Annotation*> AnnotationPrivate::findAnnotations(::Page *pdfPage, DocumentData *doc, const QSet<Annotation::SubType> &subtypes, int parentID)
{
Annots* annots = pdfPage->getAnnots();
const uint numAnnotations = annots->getNumAnnots();
if ( numAnnotations == 0 )
{
return QList<Annotation*>();
}
const bool wantTextAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AText);
const bool wantLineAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ALine);
const bool wantGeomAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AGeom);
const bool wantHighlightAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AHighlight);
const bool wantStampAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AStamp);
const bool wantInkAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AInk);
const bool wantLinkAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ALink);
const bool wantCaretAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ACaret);
const bool wantFileAttachmentAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AFileAttachment);
const bool wantSoundAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ASound);
const bool wantMovieAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AMovie);
const bool wantScreenAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AScreen);
const bool wantWidgetAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AWidget);
// Create Annotation objects and tie to their native Annot
QList<Annotation*> res;
for ( uint k = 0; k < numAnnotations; k++ )
{
// get the j-th annotation
Annot * ann = annots->getAnnot( k );
if ( !ann )
{
error(errInternal, -1, "Annot {0:ud} is null", k);
continue;
}
// Check parent annotation
AnnotMarkup * markupann = dynamic_cast< AnnotMarkup * >( ann );
if (!markupann)
{
// Assume it's a root annotation, and skip if user didn't request it
if (parentID != -1)
continue;
}
else if (markupann->getInReplyToID() != parentID)
continue;
/* Create Annotation of the right subclass */
Annotation * annotation = nullptr;
Annot::AnnotSubtype subType = ann->getType();
switch ( subType )
{
case Annot::typeText:
if (!wantTextAnnotations)
continue;
annotation = new TextAnnotation(TextAnnotation::Linked);
break;
case Annot::typeFreeText:
if (!wantTextAnnotations)
continue;
annotation = new TextAnnotation(TextAnnotation::InPlace);
break;
case Annot::typeLine:
if (!wantLineAnnotations)
continue;
annotation = new LineAnnotation(LineAnnotation::StraightLine);
break;
case Annot::typePolygon:
case Annot::typePolyLine:
if (!wantLineAnnotations)
continue;
annotation = new LineAnnotation(LineAnnotation::Polyline);
break;
case Annot::typeSquare:
case Annot::typeCircle:
if (!wantGeomAnnotations)
continue;
annotation = new GeomAnnotation();
break;
case Annot::typeHighlight:
case Annot::typeUnderline:
case Annot::typeSquiggly:
case Annot::typeStrikeOut:
if (!wantHighlightAnnotations)
continue;
annotation = new HighlightAnnotation();
break;
case Annot::typeStamp:
if (!wantStampAnnotations)
continue;
annotation = new StampAnnotation();
break;
case Annot::typeInk:
if (!wantInkAnnotations)
continue;
annotation = new InkAnnotation();
break;
case Annot::typeLink: /* TODO: Move logic to getters */
{
if (!wantLinkAnnotations)
continue;
// parse Link params
AnnotLink * linkann = static_cast< AnnotLink * >( ann );
LinkAnnotation * l = new LinkAnnotation();
annotation = l;
// -> hlMode
l->setLinkHighlightMode( (LinkAnnotation::HighlightMode)linkann->getLinkEffect() );
// -> link region
// TODO
// reading link action
if ( linkann->getAction() )
{
Link * popplerLink = PageData::convertLinkActionToLink( linkann->getAction(), doc, QRectF() );
if ( popplerLink )
{
l->setLinkDestination( popplerLink );
}
}
break;
}
case Annot::typeCaret:
if (!wantCaretAnnotations)
continue;
annotation = new CaretAnnotation();
break;
case Annot::typeFileAttachment: /* TODO: Move logic to getters */
{
if (!wantFileAttachmentAnnotations)
continue;
AnnotFileAttachment * attachann = static_cast< AnnotFileAttachment * >( ann );
FileAttachmentAnnotation * f = new FileAttachmentAnnotation();
annotation = f;
// -> fileIcon
f->setFileIconName( QString::fromLatin1( attachann->getName()->c_str() ) );
// -> embeddedFile
FileSpec *filespec = new FileSpec( attachann->getFile() );
f->setEmbeddedFile( new EmbeddedFile( *new EmbeddedFileData( filespec ) ) );
break;
}
case Annot::typeSound: /* TODO: Move logic to getters */
{
if (!wantSoundAnnotations)
continue;
AnnotSound * soundann = static_cast< AnnotSound * >( ann );
SoundAnnotation * s = new SoundAnnotation();
annotation = s;
// -> soundIcon
s->setSoundIconName( QString::fromLatin1( soundann->getName()->c_str() ) );
// -> sound
s->setSound( new SoundObject( soundann->getSound() ) );
break;
}
case Annot::typeMovie: /* TODO: Move logic to getters */
{
if (!wantMovieAnnotations)
continue;
AnnotMovie * movieann = static_cast< AnnotMovie * >( ann );
MovieAnnotation * m = new MovieAnnotation();
annotation = m;
// -> movie
MovieObject *movie = new MovieObject( movieann );
m->setMovie( movie );
// -> movieTitle
const GooString * movietitle = movieann->getTitle();
if ( movietitle )
m->setMovieTitle( QString::fromLatin1( movietitle->c_str() ) );
break;
}
case Annot::typeScreen:
{
if (!wantScreenAnnotations)
continue;
AnnotScreen * screenann = static_cast< AnnotScreen * >( ann );
// TODO Support other link types than Link::Rendition in ScreenAnnotation
if (!screenann->getAction() || screenann->getAction()->getKind() != actionRendition)
continue;
ScreenAnnotation * s = new ScreenAnnotation();
annotation = s;
// -> screen
Link * popplerLink = PageData::convertLinkActionToLink( screenann->getAction(), doc, QRectF() );
s->setAction( static_cast<Poppler::LinkRendition *>(popplerLink) );
// -> screenTitle
const GooString * screentitle = screenann->getTitle();
if ( screentitle )
s->setScreenTitle( UnicodeParsedString( screentitle ) );
break;
}
case Annot::typePopup:
continue; // popups are parsed by Annotation's window() getter
case Annot::typeUnknown:
continue; // special case for ignoring unknown annotations
case Annot::typeWidget:
if (!wantWidgetAnnotations)
continue;
annotation = new WidgetAnnotation();
break;
case Annot::typeRichMedia:
{
const AnnotRichMedia * annotRichMedia = static_cast< AnnotRichMedia * >( ann );
RichMediaAnnotation *richMediaAnnotation = new RichMediaAnnotation;
const AnnotRichMedia::Settings *annotSettings = annotRichMedia->getSettings();
if ( annotSettings ) {
RichMediaAnnotation::Settings *settings = new RichMediaAnnotation::Settings;
if ( annotSettings->getActivation() ) {
RichMediaAnnotation::Activation *activation = new RichMediaAnnotation::Activation;
switch ( annotSettings->getActivation()->getCondition() )
{
case AnnotRichMedia::Activation::conditionPageOpened:
activation->setCondition( RichMediaAnnotation::Activation::PageOpened );
break;
case AnnotRichMedia::Activation::conditionPageVisible:
activation->setCondition( RichMediaAnnotation::Activation::PageVisible );
break;
case AnnotRichMedia::Activation::conditionUserAction:
activation->setCondition( RichMediaAnnotation::Activation::UserAction );
break;
}
settings->setActivation( activation );
}
if ( annotSettings->getDeactivation() ) {
RichMediaAnnotation::Deactivation *deactivation = new RichMediaAnnotation::Deactivation;
switch ( annotSettings->getDeactivation()->getCondition() )
{
case AnnotRichMedia::Deactivation::conditionPageClosed:
deactivation->setCondition( RichMediaAnnotation::Deactivation::PageClosed );
break;
case AnnotRichMedia::Deactivation::conditionPageInvisible:
deactivation->setCondition( RichMediaAnnotation::Deactivation::PageInvisible );
break;
case AnnotRichMedia::Deactivation::conditionUserAction:
deactivation->setCondition( RichMediaAnnotation::Deactivation::UserAction );
break;
}
settings->setDeactivation( deactivation );
}
richMediaAnnotation->setSettings( settings );
}
const AnnotRichMedia::Content *annotContent = annotRichMedia->getContent();
if ( annotContent ) {
RichMediaAnnotation::Content *content = new RichMediaAnnotation::Content;
const int configurationsCount = annotContent->getConfigurationsCount();
if ( configurationsCount > 0 ) {
QList< RichMediaAnnotation::Configuration* > configurations;
for ( int i = 0; i < configurationsCount; ++i ) {
const AnnotRichMedia::Configuration *annotConfiguration = annotContent->getConfiguration( i );
if ( !annotConfiguration )
continue;
RichMediaAnnotation::Configuration *configuration = new RichMediaAnnotation::Configuration;
if ( annotConfiguration->getName() )
configuration->setName( UnicodeParsedString( annotConfiguration->getName() ) );
switch ( annotConfiguration->getType() )
{
case AnnotRichMedia::Configuration::type3D:
configuration->setType( RichMediaAnnotation::Configuration::Type3D );
break;
case AnnotRichMedia::Configuration::typeFlash:
configuration->setType( RichMediaAnnotation::Configuration::TypeFlash );
break;
case AnnotRichMedia::Configuration::typeSound:
configuration->setType( RichMediaAnnotation::Configuration::TypeSound );
break;
case AnnotRichMedia::Configuration::typeVideo:
configuration->setType( RichMediaAnnotation::Configuration::TypeVideo );
break;
}
const int instancesCount = annotConfiguration->getInstancesCount();
if ( instancesCount > 0 ) {
QList< RichMediaAnnotation::Instance* > instances;
for ( int j = 0; j < instancesCount; ++j ) {
const AnnotRichMedia::Instance *annotInstance = annotConfiguration->getInstance( j );
if ( !annotInstance )
continue;
RichMediaAnnotation::Instance *instance = new RichMediaAnnotation::Instance;
switch ( annotInstance->getType() )
{
case AnnotRichMedia::Instance::type3D:
instance->setType( RichMediaAnnotation::Instance::Type3D );
break;
case AnnotRichMedia::Instance::typeFlash:
instance->setType( RichMediaAnnotation::Instance::TypeFlash );
break;
case AnnotRichMedia::Instance::typeSound:
instance->setType( RichMediaAnnotation::Instance::TypeSound );
break;
case AnnotRichMedia::Instance::typeVideo:
instance->setType( RichMediaAnnotation::Instance::TypeVideo );
break;
}
const AnnotRichMedia::Params *annotParams = annotInstance->getParams();
if ( annotParams ) {
RichMediaAnnotation::Params *params = new RichMediaAnnotation::Params;
if ( annotParams->getFlashVars() )
params->setFlashVars( UnicodeParsedString( annotParams->getFlashVars() ) );
instance->setParams( params );
}
instances.append( instance );
}
configuration->setInstances( instances );
}
configurations.append( configuration );
}
content->setConfigurations( configurations );
}
const int assetsCount = annotContent->getAssetsCount();
if ( assetsCount > 0 ) {
QList< RichMediaAnnotation::Asset* > assets;
for ( int i = 0; i < assetsCount; ++i ) {
const AnnotRichMedia::Asset *annotAsset = annotContent->getAsset( i );
if ( !annotAsset )
continue;
RichMediaAnnotation::Asset *asset = new RichMediaAnnotation::Asset;
if ( annotAsset->getName() )
asset->setName( UnicodeParsedString( annotAsset->getName() ) );
FileSpec *fileSpec = new FileSpec( annotAsset->getFileSpec() );
asset->setEmbeddedFile( new EmbeddedFile( *new EmbeddedFileData( fileSpec ) ) );
assets.append( asset );
}
content->setAssets( assets );
}
richMediaAnnotation->setContent( content );
}
annotation = richMediaAnnotation;
break;
}
default:
{
#define CASE_FOR_TYPE( thetype ) \
case Annot::type ## thetype: \
error(errUnimplemented, -1, "Annotation " #thetype " not supported"); \
break;
switch ( subType )
{
CASE_FOR_TYPE( PrinterMark )
CASE_FOR_TYPE( TrapNet )
CASE_FOR_TYPE( Watermark )
CASE_FOR_TYPE( 3D )
default: error(errUnimplemented, -1, "Annotation {0:d} not supported", subType);
}
continue;
#undef CASE_FOR_TYPE
}
}
annotation->d_ptr->tieToNativeAnnot(ann, pdfPage, doc);
res.append(annotation);
}
return res;
}
Ref AnnotationPrivate::pdfObjectReference() const
{
if (pdfAnnot == nullptr)
{
return Ref::INVALID();
}
return pdfAnnot->getRef();
}
Link* AnnotationPrivate::additionalAction( Annotation::AdditionalActionType type ) const
{
if ( pdfAnnot->getType() != Annot::typeScreen && pdfAnnot->getType() != Annot::typeWidget )
return nullptr;
const Annot::AdditionalActionsType actionType = toPopplerAdditionalActionType(type);
::LinkAction *linkAction = nullptr;
if ( pdfAnnot->getType() == Annot::typeScreen )
linkAction = static_cast<AnnotScreen*>( pdfAnnot )->getAdditionalAction( actionType );
else
linkAction = static_cast<AnnotWidget*>( pdfAnnot )->getAdditionalAction( actionType );
Link *link = nullptr;
if ( linkAction )
link = PageData::convertLinkActionToLink( linkAction, parentDoc, QRectF() );
return link;
}
void AnnotationPrivate::addAnnotationToPage(::Page *pdfPage, DocumentData *doc, const Annotation * ann)
{
if (ann->d_ptr->pdfAnnot != nullptr)
{
error(errIO, -1, "Annotation is already tied");
return;
}
// Unimplemented annotations can't be created by the user because their ctor
// is private. Therefore, createNativeAnnot will never return 0
Annot *nativeAnnot = ann->d_ptr->createNativeAnnot(pdfPage, doc);
Q_ASSERT(nativeAnnot);
pdfPage->addAnnot(nativeAnnot);
}
void AnnotationPrivate::removeAnnotationFromPage(::Page *pdfPage, const Annotation * ann)
{
if (ann->d_ptr->pdfAnnot == nullptr)
{
error(errIO, -1, "Annotation is not tied");
return;
}
if (ann->d_ptr->pdfPage != pdfPage)
{
error(errIO, -1, "Annotation doesn't belong to the specified page");
return;
}
// Remove annotation
pdfPage->removeAnnot(ann->d_ptr->pdfAnnot);
// Destroy object
delete ann;
}
class Annotation::Style::Private : public QSharedData
{
public:
Private()
: opacity( 1.0 ), width( 1.0 ), lineStyle( Solid ), xCorners( 0.0 ),
yCorners( 0.0 ), lineEffect( NoEffect ), effectIntensity( 1.0 )
{
dashArray.resize(1);
dashArray[0] = 3;
}
QColor color;
double opacity;
double width;
Annotation::LineStyle lineStyle;
double xCorners;
double yCorners;
QVector<double> dashArray;
Annotation::LineEffect lineEffect;
double effectIntensity;
};
Annotation::Style::Style()
: d ( new Private )
{
}
Annotation::Style::Style( const Style &other )
: d( other.d )
{
}
Annotation::Style& Annotation::Style::operator=( const Style &other )
{
if ( this != &other )
d = other.d;
return *this;
}
Annotation::Style::~Style()
{
}
QColor Annotation::Style::color() const
{
return d->color;
}
void Annotation::Style::setColor(const QColor &color)
{
d->color = color;
}
double Annotation::Style::opacity() const
{
return d->opacity;
}
void Annotation::Style::setOpacity(double opacity)
{
d->opacity = opacity;
}
double Annotation::Style::width() const
{
return d->width;
}
void Annotation::Style::setWidth(double width)
{
d->width = width;
}
Annotation::LineStyle Annotation::Style::lineStyle() const
{
return d->lineStyle;
}
void Annotation::Style::setLineStyle(Annotation::LineStyle style)
{
d->lineStyle = style;
}
double Annotation::Style::xCorners() const
{
return d->xCorners;
}
void Annotation::Style::setXCorners(double radius)
{
d->xCorners = radius;
}
double Annotation::Style::yCorners() const
{
return d->yCorners;
}
void Annotation::Style::setYCorners(double radius)
{
d->yCorners = radius;
}
const QVector<double>& Annotation::Style::dashArray() const
{
return d->dashArray;
}
void Annotation::Style::setDashArray(const QVector<double> &array)
{
d->dashArray = array;
}
Annotation::LineEffect Annotation::Style::lineEffect() const
{
return d->lineEffect;
}
void Annotation::Style::setLineEffect(Annotation::LineEffect effect)
{
d->lineEffect = effect;
}
double Annotation::Style::effectIntensity() const
{
return d->effectIntensity;
}
void Annotation::Style::setEffectIntensity(double intens)
{
d->effectIntensity = intens;
}
class Annotation::Popup::Private : public QSharedData
{
public:
Private()
: flags( -1 ) {}
int flags;
QRectF geometry;
QString title;
QString summary;
QString text;
};
Annotation::Popup::Popup()
: d ( new Private )
{
}
Annotation::Popup::Popup( const Popup &other )
: d( other.d )
{
}
Annotation::Popup& Annotation::Popup::operator=( const Popup &other )
{
if ( this != &other )
d = other.d;
return *this;
}
Annotation::Popup::~Popup()
{
}
int Annotation::Popup::flags() const
{
return d->flags;
}
void Annotation::Popup::setFlags( int flags )
{
d->flags = flags;
}
QRectF Annotation::Popup::geometry() const
{
return d->geometry;
}
void Annotation::Popup::setGeometry( const QRectF &geom )
{
d->geometry = geom;
}
QString Annotation::Popup::title() const
{
return d->title;
}
void Annotation::Popup::setTitle( const QString &title )
{
d->title = title;
}
QString Annotation::Popup::summary() const
{
return d->summary;
}
void Annotation::Popup::setSummary( const QString &summary )
{
d->summary = summary;
}
QString Annotation::Popup::text() const
{
return d->text;
}
void Annotation::Popup::setText( const QString &text )
{
d->text = text;
}
Annotation::Annotation( AnnotationPrivate &dd )
: d_ptr( &dd )
{
}
Annotation::~Annotation()
{
}
Annotation::Annotation( AnnotationPrivate &dd, const QDomNode &annNode )
: d_ptr( &dd )
{
Q_D( Annotation );
// get the [base] element of the annotation node
QDomElement e = AnnotationUtils::findChildElement( annNode, QStringLiteral("base") );
if ( e.isNull() )
return;
Style s;
Popup w;
// parse -contents- attributes
if ( e.hasAttribute( QStringLiteral("author") ) )
setAuthor(e.attribute( QStringLiteral("author") ));
if ( e.hasAttribute( QStringLiteral("contents") ) )
setContents(e.attribute( QStringLiteral("contents") ));
if ( e.hasAttribute( QStringLiteral("uniqueName") ) )
setUniqueName(e.attribute( QStringLiteral("uniqueName") ));
if ( e.hasAttribute( QStringLiteral("modifyDate") ) )
setModificationDate(QDateTime::fromString( e.attribute( QStringLiteral("modifyDate") ) ));
if ( e.hasAttribute( QStringLiteral("creationDate") ) )
setCreationDate(QDateTime::fromString( e.attribute( QStringLiteral("creationDate") ) ));
// parse -other- attributes
if ( e.hasAttribute( QStringLiteral("flags") ) )
setFlags(e.attribute( QStringLiteral("flags") ).toInt());
if ( e.hasAttribute( QStringLiteral("color") ) )
s.setColor(QColor( e.attribute( QStringLiteral("color") ) ));
if ( e.hasAttribute( QStringLiteral("opacity") ) )
s.setOpacity(e.attribute( QStringLiteral("opacity") ).toDouble());
// parse -the-subnodes- (describing Style, Window, Revision(s) structures)
// Note: all subnodes if present must be 'attributes complete'
QDomNode eSubNode = e.firstChild();
while ( eSubNode.isElement() )
{
QDomElement ee = eSubNode.toElement();
eSubNode = eSubNode.nextSibling();
// parse boundary
if ( ee.tagName() == QLatin1String("boundary") )
{
QRectF brect;
brect.setLeft(ee.attribute( QStringLiteral("l") ).toDouble());
brect.setTop(ee.attribute( QStringLiteral("t") ).toDouble());
brect.setRight(ee.attribute( QStringLiteral("r") ).toDouble());
brect.setBottom(ee.attribute( QStringLiteral("b") ).toDouble());
setBoundary(brect);
}
// parse penStyle if not default
else if ( ee.tagName() == QLatin1String("penStyle") )
{
s.setWidth(ee.attribute( QStringLiteral("width") ).toDouble());
s.setLineStyle((LineStyle)ee.attribute( QStringLiteral("style") ).toInt());
s.setXCorners(ee.attribute( QStringLiteral("xcr") ).toDouble());
s.setYCorners(ee.attribute( QStringLiteral("ycr") ).toDouble());
// Try to parse dash array (new format)
QVector<double> dashArray;
QDomNode eeSubNode = ee.firstChild();
while ( eeSubNode.isElement() )
{
QDomElement eee = eeSubNode.toElement();
eeSubNode = eeSubNode.nextSibling();
if ( eee.tagName() != QLatin1String("dashsegm") )
continue;
dashArray.append(eee.attribute( QStringLiteral("len") ).toDouble());
}
// If no segments were found use marks/spaces (old format)
if ( dashArray.size() == 0 )
{
dashArray.append(ee.attribute( QStringLiteral("marks") ).toDouble());
dashArray.append(ee.attribute( QStringLiteral("spaces") ).toDouble());
}
s.setDashArray(dashArray);
}
// parse effectStyle if not default
else if ( ee.tagName() == QLatin1String("penEffect") )
{
s.setLineEffect((LineEffect)ee.attribute( QStringLiteral("effect") ).toInt());
s.setEffectIntensity(ee.attribute( QStringLiteral("intensity") ).toDouble());
}
// parse window if present
else if ( ee.tagName() == QLatin1String("window") )
{
QRectF geom;
geom.setX(ee.attribute( QStringLiteral("top") ).toDouble());
geom.setY(ee.attribute( QStringLiteral("left") ).toDouble());
if (ee.hasAttribute(QStringLiteral("widthDouble")))
geom.setWidth(ee.attribute( QStringLiteral("widthDouble") ).toDouble());
else
geom.setWidth(ee.attribute( QStringLiteral("width") ).toDouble());
if (ee.hasAttribute(QStringLiteral("widthDouble")))
geom.setHeight(ee.attribute( QStringLiteral("heightDouble") ).toDouble());
else
geom.setHeight(ee.attribute( QStringLiteral("height") ).toDouble());
w.setGeometry(geom);
w.setFlags(ee.attribute( QStringLiteral("flags") ).toInt());
w.setTitle(ee.attribute( QStringLiteral("title") ));
w.setSummary(ee.attribute( QStringLiteral("summary") ));
// parse window subnodes
QDomNode winNode = ee.firstChild();
for ( ; winNode.isElement(); winNode = winNode.nextSibling() )
{
QDomElement winElement = winNode.toElement();
if ( winElement.tagName() == QLatin1String("text") )
w.setText(winElement.firstChild().toCDATASection().data());
}
}
}
setStyle(s); // assign parsed style
setPopup(w); // assign parsed window
// get the [revisions] element of the annotation node
QDomNode revNode = annNode.firstChild();
for ( ; revNode.isElement(); revNode = revNode.nextSibling() )
{
QDomElement revElement = revNode.toElement();
if ( revElement.tagName() != QLatin1String("revision") )
continue;
Annotation *reply = AnnotationUtils::createAnnotation( revElement );
if (reply) // if annotation is valid, add as a revision of this annotation
{
RevScope scope = (RevScope)revElement.attribute( QStringLiteral("revScope") ).toInt();
RevType type = (RevType)revElement.attribute( QStringLiteral("revType") ).toInt();
d->addRevision(reply, scope, type);
delete reply;
}
}
}
void Annotation::storeBaseAnnotationProperties( QDomNode & annNode, QDomDocument & document ) const
{
// create [base] element of the annotation node
QDomElement e = document.createElement( QStringLiteral("base") );
annNode.appendChild( e );
const Style s = style();
const Popup w = popup();
// store -contents- attributes
if ( !author().isEmpty() )
e.setAttribute( QStringLiteral("author"), author() );
if ( !contents().isEmpty() )
e.setAttribute( QStringLiteral("contents"), contents() );
if ( !uniqueName().isEmpty() )
e.setAttribute( QStringLiteral("uniqueName"), uniqueName() );
if ( modificationDate().isValid() )
e.setAttribute( QStringLiteral("modifyDate"), modificationDate().toString() );
if ( creationDate().isValid() )
e.setAttribute( QStringLiteral("creationDate"), creationDate().toString() );
// store -other- attributes
if ( flags() )
e.setAttribute( QStringLiteral("flags"), flags() );
if ( s.color().isValid() )
e.setAttribute( QStringLiteral("color"), s.color().name() );
if ( s.opacity() != 1.0 )
e.setAttribute( QStringLiteral("opacity"), QString::number( s.opacity() ) );
// Sub-Node-1 - boundary
const QRectF brect = boundary();
QDomElement bE = document.createElement( QStringLiteral("boundary") );
e.appendChild( bE );
bE.setAttribute( QStringLiteral("l"), QString::number( (double)brect.left() ) );
bE.setAttribute( QStringLiteral("t"), QString::number( (double)brect.top() ) );
bE.setAttribute( QStringLiteral("r"), QString::number( (double)brect.right() ) );
bE.setAttribute( QStringLiteral("b"), QString::number( (double)brect.bottom() ) );
// Sub-Node-2 - penStyle
const QVector<double> dashArray = s.dashArray();
if ( s.width() != 1 || s.lineStyle() != Solid || s.xCorners() != 0 ||
s.yCorners() != 0.0 || dashArray.size() != 1 || dashArray[0] != 3 )
{
QDomElement psE = document.createElement( QStringLiteral("penStyle") );
e.appendChild( psE );
psE.setAttribute( QStringLiteral("width"), QString::number( s.width() ) );
psE.setAttribute( QStringLiteral("style"), (int)s.lineStyle() );
psE.setAttribute( QStringLiteral("xcr"), QString::number( s.xCorners() ) );
psE.setAttribute( QStringLiteral("ycr"), QString::number( s.yCorners() ) );
int marks = 3, spaces = 0; // Do not break code relying on marks/spaces
if (dashArray.size() != 0)
marks = (int)dashArray[0];
if (dashArray.size() > 1)
spaces = (int)dashArray[1];
psE.setAttribute( QStringLiteral("marks"), marks );
psE.setAttribute( QStringLiteral("spaces"), spaces );
foreach (double segm, dashArray)
{
QDomElement pattE = document.createElement( QStringLiteral("dashsegm") );
pattE.setAttribute( QStringLiteral("len"), QString::number( segm ) );
psE.appendChild(pattE);
}
}
// Sub-Node-3 - penEffect
if ( s.lineEffect() != NoEffect || s.effectIntensity() != 1.0 )
{
QDomElement peE = document.createElement( QStringLiteral("penEffect") );
e.appendChild( peE );
peE.setAttribute( QStringLiteral("effect"), (int)s.lineEffect() );
peE.setAttribute( QStringLiteral("intensity"), QString::number( s.effectIntensity() ) );
}
// Sub-Node-4 - window
if ( w.flags() != -1 || !w.title().isEmpty() || !w.summary().isEmpty() ||
!w.text().isEmpty() )
{
QDomElement wE = document.createElement( QStringLiteral("window") );
const QRectF geom = w.geometry();
e.appendChild( wE );
wE.setAttribute( QStringLiteral("flags"), w.flags() );
wE.setAttribute( QStringLiteral("top"), QString::number( geom.x() ) );
wE.setAttribute( QStringLiteral("left"), QString::number( geom.y() ) );
wE.setAttribute( QStringLiteral("width"), (int)geom.width() );
wE.setAttribute( QStringLiteral("height"), (int)geom.height() );
wE.setAttribute( QStringLiteral("widthDouble"), QString::number( geom.width() ) );
wE.setAttribute( QStringLiteral("heightDouble"), QString::number( geom.height() ) );
wE.setAttribute( QStringLiteral("title"), w.title() );
wE.setAttribute( QStringLiteral("summary"), w.summary() );
// store window.text as a subnode, because we need escaped data
if ( !w.text().isEmpty() )
{
QDomElement escapedText = document.createElement( QStringLiteral("text") );
wE.appendChild( escapedText );
QDomCDATASection textCData = document.createCDATASection( w.text() );
escapedText.appendChild( textCData );
}
}
const QList<Annotation*> revs = revisions();
// create [revision] element of the annotation node (if any)
if ( revs.isEmpty() )
return;
// add all revisions as children of revisions element
foreach (const Annotation *rev, revs)
{
QDomElement r = document.createElement( QStringLiteral("revision") );
annNode.appendChild( r );
// set element attributes
r.setAttribute( QStringLiteral("revScope"), (int)rev->revisionScope() );
r.setAttribute( QStringLiteral("revType"), (int)rev->revisionType() );
// use revision as the annotation element, so fill it up
AnnotationUtils::storeAnnotation( rev, r, document );
delete rev;
}
}
QString Annotation::author() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
return d->author;
const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup*>(d->pdfAnnot);
return markupann ? UnicodeParsedString( markupann->getLabel() ) : QString();
}
void Annotation::setAuthor( const QString &author )
{
Q_D( Annotation );
if (!d->pdfAnnot)
{
d->author = author;
return;
}
AnnotMarkup *markupann = dynamic_cast<AnnotMarkup*>(d->pdfAnnot);
if (markupann)
{
GooString *s = QStringToUnicodeGooString(author);
markupann->setLabel(s);
delete s;
}
}
QString Annotation::contents() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
return d->contents;
return UnicodeParsedString( d->pdfAnnot->getContents() );
}
void Annotation::setContents( const QString &contents )
{
Q_D( Annotation );
if (!d->pdfAnnot)
{
d->contents = contents;
return;
}
GooString *s = QStringToUnicodeGooString(contents);
d->pdfAnnot->setContents(s);
delete s;
}
QString Annotation::uniqueName() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
return d->uniqueName;
return UnicodeParsedString( d->pdfAnnot->getName() );
}
void Annotation::setUniqueName( const QString &uniqueName )
{
Q_D( Annotation );
if (!d->pdfAnnot)
{
d->uniqueName = uniqueName;
return;
}
QByteArray ascii = uniqueName.toLatin1();
GooString s(ascii.constData());
d->pdfAnnot->setName(&s);
}
QDateTime Annotation::modificationDate() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
return d->modDate;
if ( d->pdfAnnot->getModified() )
return convertDate( d->pdfAnnot->getModified()->c_str() );
else
return QDateTime();
}
void Annotation::setModificationDate( const QDateTime &date )
{
Q_D( Annotation );
if (!d->pdfAnnot)
{
d->modDate = date;
return;
}
#if 0 // TODO: Conversion routine is broken
if (d->pdfAnnot)
{
time_t t = date.toTime_t();
GooString *s = timeToDateString(&t);
d->pdfAnnot->setModified(s);
delete s;
}
#endif
}
QDateTime Annotation::creationDate() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
return d->creationDate;
const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup*>(d->pdfAnnot);
if (markupann && markupann->getDate())
return convertDate( markupann->getDate()->c_str() );
return modificationDate();
}
void Annotation::setCreationDate( const QDateTime &date )
{
Q_D( Annotation );
if (!d->pdfAnnot)
{
d->creationDate = date;
return;
}
#if 0 // TODO: Conversion routine is broken
AnnotMarkup *markupann = dynamic_cast<AnnotMarkup*>(d->pdfAnnot);
if (markupann)
{
time_t t = date.toTime_t();
GooString *s = timeToDateString(&t);
markupann->setDate(s);
delete s;
}
#endif
}
static int fromPdfFlags(int flags)
{
int qtflags = 0;
if ( flags & Annot::flagHidden )
qtflags |= Annotation::Hidden;
if ( flags & Annot::flagNoZoom )
qtflags |= Annotation::FixedSize;
if ( flags & Annot::flagNoRotate )
qtflags |= Annotation::FixedRotation;
if ( !( flags & Annot::flagPrint ) )
qtflags |= Annotation::DenyPrint;
if ( flags & Annot::flagReadOnly )
qtflags |= (Annotation::DenyWrite | Annotation::DenyDelete);
if ( flags & Annot::flagLocked )
qtflags |= Annotation::DenyDelete;
if ( flags & Annot::flagToggleNoView )
qtflags |= Annotation::ToggleHidingOnMouse;
return qtflags;
}
static int toPdfFlags(int qtflags)
{
int flags = 0;
if ( qtflags & Annotation::Hidden )
flags |= Annot::flagHidden;
if ( qtflags & Annotation::FixedSize )
flags |= Annot::flagNoZoom;
if ( qtflags & Annotation::FixedRotation )
flags |= Annot::flagNoRotate;
if ( !( qtflags & Annotation::DenyPrint ) )
flags |= Annot::flagPrint;
if ( qtflags & Annotation::DenyWrite )
flags |= Annot::flagReadOnly;
if ( qtflags & Annotation::DenyDelete )
flags |= Annot::flagLocked;
if ( qtflags & Annotation::ToggleHidingOnMouse )
flags |= Annot::flagToggleNoView;
return flags;
}
int Annotation::flags() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
return d->flags;
return fromPdfFlags( d->pdfAnnot->getFlags() );
}
void Annotation::setFlags( int flags )
{
Q_D( Annotation );
if (!d->pdfAnnot)
{
d->flags = flags;
return;
}
d->pdfAnnot->setFlags(toPdfFlags( flags ));
}
QRectF Annotation::boundary() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
return d->boundary;
const PDFRectangle * rect = d->pdfAnnot->getRect();
return d->fromPdfRectangle( *rect );
}
void Annotation::setBoundary( const QRectF &boundary )
{
Q_D( Annotation );
if (!d->pdfAnnot)
{
d->boundary = boundary;
return;
}
PDFRectangle rect = d->boundaryToPdfRectangle( boundary, flags() );
d->pdfAnnot->setRect(&rect);
}
Annotation::Style Annotation::style() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
return d->style;
Style s;
s.setColor(convertAnnotColor( d->pdfAnnot->getColor() ));
const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup*>(d->pdfAnnot);
if (markupann)
s.setOpacity( markupann->getOpacity() );
const AnnotBorder *border = d->pdfAnnot->getBorder();
if (border)
{
if ( border->getType() == AnnotBorder::typeArray )
{
const AnnotBorderArray *border_array = static_cast<const AnnotBorderArray*>(border);
s.setXCorners( border_array->getHorizontalCorner() );
s.setYCorners( border_array->getVerticalCorner() );
}
s.setWidth( border->getWidth() );
s.setLineStyle((Annotation::LineStyle)( 1 << border->getStyle() ));
const int dashArrLen = border->getDashLength();
const double* dashArrData = border->getDash();
QVector<double> dashArrVect( dashArrLen );
for (int i = 0; i < dashArrLen; ++i)
dashArrVect[i] = dashArrData[i];
s.setDashArray(dashArrVect);
}
AnnotBorderEffect *border_effect;
switch (d->pdfAnnot->getType())
{
case Annot::typeFreeText:
border_effect = static_cast<AnnotFreeText*>(d->pdfAnnot)->getBorderEffect();
break;
case Annot::typeSquare:
case Annot::typeCircle:
border_effect = static_cast<AnnotGeometry*>(d->pdfAnnot)->getBorderEffect();
break;
default:
border_effect = nullptr;
}
if (border_effect)
{
s.setLineEffect( (Annotation::LineEffect)border_effect->getEffectType() );
s.setEffectIntensity( border_effect->getIntensity() );
}
return s;
}
void Annotation::setStyle( const Annotation::Style& style )
{
Q_D( Annotation );
if (!d->pdfAnnot)
{
d->style = style;
return;
}
d->pdfAnnot->setColor(convertQColor( style.color() ));
AnnotMarkup *markupann = dynamic_cast<AnnotMarkup*>(d->pdfAnnot);
if (markupann)
markupann->setOpacity( style.opacity() );
auto border = std::make_unique<AnnotBorderArray>();
border->setWidth( style.width() );
border->setHorizontalCorner( style.xCorners() );
border->setVerticalCorner( style.yCorners() );
d->pdfAnnot->setBorder(std::move(border));
}
Annotation::Popup Annotation::popup() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
return d->popup;
Popup w;
AnnotPopup *popup = nullptr;
int flags = -1; // Not initialized
const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup*>(d->pdfAnnot);
if (markupann)
{
popup = markupann->getPopup();
w.setSummary(UnicodeParsedString( markupann->getSubject() ));
}
if (popup)
{
flags = fromPdfFlags( popup->getFlags() ) & ( Annotation::Hidden |
Annotation::FixedSize | Annotation::FixedRotation );
if (!popup->getOpen())
flags |= Annotation::Hidden;
const PDFRectangle * rect = popup->getRect();
w.setGeometry( d->fromPdfRectangle( *rect ) );
}
if (d->pdfAnnot->getType() == Annot::typeText)
{
const AnnotText * textann = static_cast<const AnnotText*>(d->pdfAnnot);
// Text annotations default to same rect as annotation
if (flags == -1)
{
flags = 0;
w.setGeometry( boundary() );
}
// If text is not 'opened', force window hiding. if the window
// was parsed from popup, the flag should already be set
if ( !textann->getOpen() && flags != -1 )
flags |= Annotation::Hidden;
}
w.setFlags(flags);
return w;
}
void Annotation::setPopup( const Annotation::Popup& popup )
{
Q_D( Annotation );
if (!d->pdfAnnot)
{
d->popup = popup;
return;
}
#if 0 /* TODO: Remove old popup and add AnnotPopup to page */
AnnotMarkup *markupann = dynamic_cast<AnnotMarkup*>(d->pdfAnnot);
if (!markupann)
return;
// Create a new AnnotPopup and assign it to pdfAnnot
PDFRectangle rect = d->toPdfRectangle( popup.geometry() );
AnnotPopup * p = new AnnotPopup( d->pdfPage->getDoc(), &rect );
p->setOpen( !(popup.flags() & Annotation::Hidden) );
if (!popup.summary().isEmpty())
{
GooString *s = QStringToUnicodeGooString(popup.summary());
markupann->setLabel(s);
delete s;
}
markupann->setPopup(p);
#endif
}
Annotation::RevScope Annotation::revisionScope() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
return d->revisionScope;
const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup*>(d->pdfAnnot);
if (markupann && markupann->isInReplyTo())
{
switch (markupann->getReplyTo())
{
case AnnotMarkup::replyTypeR:
return Annotation::Reply;
case AnnotMarkup::replyTypeGroup:
return Annotation::Group;
}
}
return Annotation::Root; // It's not a revision
}
Annotation::RevType Annotation::revisionType() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
return d->revisionType;
const AnnotText *textann = dynamic_cast<const AnnotText*>(d->pdfAnnot);
if (textann && textann->isInReplyTo())
{
switch (textann->getState())
{
case AnnotText::stateMarked:
return Annotation::Marked;
case AnnotText::stateUnmarked:
return Annotation::Unmarked;
case AnnotText::stateAccepted:
return Annotation::Accepted;
case AnnotText::stateRejected:
return Annotation::Rejected;
case AnnotText::stateCancelled:
return Annotation::Cancelled;
case AnnotText::stateCompleted:
return Annotation::Completed;
default:
break;
}
}
return Annotation::None;
}
QList<Annotation*> Annotation::revisions() const
{
Q_D( const Annotation );
if (!d->pdfAnnot)
{
/* Return aliases, whose ownership goes to the caller */
QList<Annotation*> res;
foreach (Annotation *rev, d->revisions)
res.append( rev->d_ptr->makeAlias() );
return res;
}
/* If the annotation doesn't live in a object on its own (eg bug51361), it
* has no ref, therefore it can't have revisions */
if ( !d->pdfAnnot->getHasRef() )
return QList<Annotation*>();
return AnnotationPrivate::findAnnotations( d->pdfPage, d->parentDoc, QSet<Annotation::SubType>(), d->pdfAnnot->getId() );
}
//END Annotation implementation
/** TextAnnotation [Annotation] */
class TextAnnotationPrivate : public AnnotationPrivate
{
public:
TextAnnotationPrivate();
Annotation * makeAlias() override;
Annot* createNativeAnnot(::Page *destPage, DocumentData *doc) override;
void setDefaultAppearanceToNative();
std::unique_ptr<DefaultAppearance> getDefaultAppearanceFromNative() const;
// data fields
TextAnnotation::TextType textType;
QString textIcon;
QFont textFont;
QColor textColor;
int inplaceAlign; // 0:left, 1:center, 2:right
QVector<QPointF> inplaceCallout;
TextAnnotation::InplaceIntent inplaceIntent;
};
TextAnnotationPrivate::TextAnnotationPrivate()
: AnnotationPrivate(), textType( TextAnnotation::Linked ),
textIcon( QStringLiteral("Note") ), inplaceAlign( 0 ),
inplaceIntent( TextAnnotation::Unknown )
{
}
Annotation * TextAnnotationPrivate::makeAlias()
{
return new TextAnnotation(*this);
}
Annot* TextAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
{
// Setters are defined in the public class
TextAnnotation *q = static_cast<TextAnnotation*>( makeAlias() );
// Set page and contents
pdfPage = destPage;
parentDoc = doc;
// Set pdfAnnot
PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
if (textType == TextAnnotation::Linked)
{
pdfAnnot = new AnnotText{ destPage->getDoc(), &rect };
}
else
{
DefaultAppearance da{ { objName, "Invalid_font" }, static_cast<double>( textFont.pointSize() ), std::unique_ptr<AnnotColor>{ convertQColor( textColor ) } };
pdfAnnot = new AnnotFreeText{ destPage->getDoc(), &rect, da };
}
// Set properties
flushBaseAnnotationProperties();
q->setTextIcon(textIcon);
q->setInplaceAlign(inplaceAlign);
q->setCalloutPoints(inplaceCallout);
q->setInplaceIntent(inplaceIntent);
delete q;
inplaceCallout.clear(); // Free up memory
return pdfAnnot;
}
void TextAnnotationPrivate::setDefaultAppearanceToNative()
{
if ( pdfAnnot && pdfAnnot->getType() == Annot::typeFreeText )
{
AnnotFreeText * ftextann = static_cast<AnnotFreeText*>( pdfAnnot );
DefaultAppearance da{ { objName, "Invalid_font" }, static_cast<double>( textFont.pointSize() ), std::unique_ptr<AnnotColor>{ convertQColor( textColor ) } };
ftextann->setDefaultAppearance( da );
}
}
std::unique_ptr<DefaultAppearance> TextAnnotationPrivate::getDefaultAppearanceFromNative() const
{
if ( pdfAnnot && pdfAnnot->getType() == Annot::typeFreeText )
{
AnnotFreeText * ftextann = static_cast<AnnotFreeText*>( pdfAnnot );
return ftextann->getDefaultAppearance();
}
else
{
return {};
}
}
TextAnnotation::TextAnnotation( TextAnnotation::TextType type )
: Annotation( *new TextAnnotationPrivate() )
{
setTextType( type );
}
TextAnnotation::TextAnnotation(TextAnnotationPrivate &dd)
: Annotation( dd )
{}
TextAnnotation::TextAnnotation( const QDomNode & node )
: Annotation( *new TextAnnotationPrivate, node )
{
// loop through the whole children looking for a 'text' element
QDomNode subNode = node.firstChild();
while( subNode.isElement() )
{
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
if ( e.tagName() != QLatin1String("text") )
continue;
// parse the attributes
if ( e.hasAttribute( QStringLiteral("type") ) )
setTextType((TextAnnotation::TextType)e.attribute( QStringLiteral("type") ).toInt());
if ( e.hasAttribute( QStringLiteral("icon") ) )
setTextIcon(e.attribute( QStringLiteral("icon") ));
if ( e.hasAttribute( QStringLiteral("font") ) )
{
QFont font;
font.fromString( e.attribute( QStringLiteral("font") ) );
setTextFont(font);
if ( e.hasAttribute( QStringLiteral("fontColor") ) )
{
const QColor color = QColor(e.attribute( QStringLiteral("fontColor") ) );
setTextColor(color);
}
}
if ( e.hasAttribute( QStringLiteral("align") ) )
setInplaceAlign(e.attribute( QStringLiteral("align") ).toInt());
if ( e.hasAttribute( QStringLiteral("intent") ) )
setInplaceIntent((TextAnnotation::InplaceIntent)e.attribute( QStringLiteral("intent") ).toInt());
// parse the subnodes
QDomNode eSubNode = e.firstChild();
while ( eSubNode.isElement() )
{
QDomElement ee = eSubNode.toElement();
eSubNode = eSubNode.nextSibling();
if ( ee.tagName() == QLatin1String("escapedText") )
{
setContents(ee.firstChild().toCDATASection().data());
}
else if ( ee.tagName() == QLatin1String("callout") )
{
QVector<QPointF> points(3);
points[0] = QPointF(ee.attribute( QStringLiteral("ax") ).toDouble(),
ee.attribute( QStringLiteral("ay") ).toDouble());
points[1] = QPointF(ee.attribute( QStringLiteral("bx") ).toDouble(),
ee.attribute( QStringLiteral("by") ).toDouble());
points[2] = QPointF(ee.attribute( QStringLiteral("cx") ).toDouble(),
ee.attribute( QStringLiteral("cy") ).toDouble());
setCalloutPoints(points);
}
}
// loading complete
break;
}
}
TextAnnotation::~TextAnnotation()
{
}
void TextAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
// store base annotation properties
storeBaseAnnotationProperties( node, document );
// create [text] element
QDomElement textElement = document.createElement( QStringLiteral("text") );
node.appendChild( textElement );
// store the optional attributes
if ( textType() != Linked )
textElement.setAttribute( QStringLiteral("type"), (int)textType() );
if ( textIcon() != QLatin1String("Note") )
textElement.setAttribute( QStringLiteral("icon"), textIcon() );
if ( inplaceAlign() )
textElement.setAttribute( QStringLiteral("align"), inplaceAlign() );
if ( inplaceIntent() != Unknown )
textElement.setAttribute( QStringLiteral("intent"), (int)inplaceIntent() );
textElement.setAttribute( QStringLiteral("font"), textFont().toString() );
textElement.setAttribute( QStringLiteral("fontColor"), textColor().name() );
// Sub-Node-1 - escapedText
if ( !contents().isEmpty() )
{
QDomElement escapedText = document.createElement( QStringLiteral("escapedText") );
textElement.appendChild( escapedText );
QDomCDATASection textCData = document.createCDATASection( contents() );
escapedText.appendChild( textCData );
}
// Sub-Node-2 - callout
if ( calloutPoint(0).x() != 0.0 )
{
QDomElement calloutElement = document.createElement( QStringLiteral("callout") );
textElement.appendChild( calloutElement );
calloutElement.setAttribute( QStringLiteral("ax"), QString::number( calloutPoint(0).x() ) );
calloutElement.setAttribute( QStringLiteral("ay"), QString::number( calloutPoint(0).y() ) );
calloutElement.setAttribute( QStringLiteral("bx"), QString::number( calloutPoint(1).x() ) );
calloutElement.setAttribute( QStringLiteral("by"), QString::number( calloutPoint(1).y() ) );
calloutElement.setAttribute( QStringLiteral("cx"), QString::number( calloutPoint(2).x() ) );
calloutElement.setAttribute( QStringLiteral("cy"), QString::number( calloutPoint(2).y() ) );
}
}
Annotation::SubType TextAnnotation::subType() const
{
return AText;
}
TextAnnotation::TextType TextAnnotation::textType() const
{
Q_D( const TextAnnotation );
if (!d->pdfAnnot)
return d->textType;
return d->pdfAnnot->getType() == Annot::typeText ?
TextAnnotation::Linked : TextAnnotation::InPlace;
}
void TextAnnotation::setTextType( TextAnnotation::TextType type )
{
Q_D( TextAnnotation );
if (!d->pdfAnnot)
{
d->textType = type;
return;
}
// Type cannot be changed if annotation is already tied
}
QString TextAnnotation::textIcon() const
{
Q_D( const TextAnnotation );
if (!d->pdfAnnot)
return d->textIcon;
if (d->pdfAnnot->getType() == Annot::typeText)
{
const AnnotText * textann = static_cast<const AnnotText*>(d->pdfAnnot);
return QString::fromLatin1( textann->getIcon()->c_str() );
}
return QString();
}
void TextAnnotation::setTextIcon( const QString &icon )
{
Q_D( TextAnnotation );
if (!d->pdfAnnot)
{
d->textIcon = icon;
return;
}
if (d->pdfAnnot->getType() == Annot::typeText)
{
AnnotText * textann = static_cast<AnnotText*>(d->pdfAnnot);
QByteArray encoded = icon.toLatin1();
GooString s(encoded.constData());
textann->setIcon(&s);
}
}
QFont TextAnnotation::textFont() const
{
Q_D( const TextAnnotation );
if ( !d->pdfAnnot )
return d->textFont;
double fontSize { AnnotFreeText::undefinedFontPtSize };
if ( d->pdfAnnot->getType() == Annot::typeFreeText )
{
std::unique_ptr<DefaultAppearance> da{ d->getDefaultAppearanceFromNative() };
if ( da && da->getFontPtSize() > 0 )
{
fontSize = da->getFontPtSize();
}
}
QFont font;
font.setPointSizeF( fontSize );
return font;
}
void TextAnnotation::setTextFont( const QFont &font )
{
Q_D( TextAnnotation );
d->textFont = font;
d->textColor = Qt::black;
d->setDefaultAppearanceToNative();
}
QColor TextAnnotation::textColor() const
{
Q_D( const TextAnnotation );
if ( !d->pdfAnnot )
return d->textColor;
if ( std::unique_ptr<DefaultAppearance> da{ d->getDefaultAppearanceFromNative() } )
{
return convertAnnotColor( da->getFontColor() );
}
return {};
}
void TextAnnotation::setTextColor( const QColor &color )
{
Q_D( TextAnnotation );
d->textColor = color;
d->setDefaultAppearanceToNative();
}
int TextAnnotation::inplaceAlign() const
{
Q_D( const TextAnnotation );
if (!d->pdfAnnot)
return d->inplaceAlign;
if (d->pdfAnnot->getType() == Annot::typeFreeText)
{
const AnnotFreeText * ftextann = static_cast<const AnnotFreeText*>(d->pdfAnnot);
return ftextann->getQuadding();
}
return 0;
}
void TextAnnotation::setInplaceAlign( int align )
{
Q_D( TextAnnotation );
if (!d->pdfAnnot)
{
d->inplaceAlign = align;
return;
}
if (d->pdfAnnot->getType() == Annot::typeFreeText)
{
AnnotFreeText * ftextann = static_cast<AnnotFreeText*>(d->pdfAnnot);
ftextann->setQuadding((AnnotFreeText::AnnotFreeTextQuadding)align);
}
}
QPointF TextAnnotation::calloutPoint( int id ) const
{
const QVector<QPointF> points = calloutPoints();
if ( id < 0 || id >= points.size() )
return QPointF();
else
return points[id];
}
QVector<QPointF> TextAnnotation::calloutPoints() const
{
Q_D( const TextAnnotation );
if (!d->pdfAnnot)
return d->inplaceCallout;
if (d->pdfAnnot->getType() == Annot::typeText)
return QVector<QPointF>();
const AnnotFreeText * ftextann = static_cast<const AnnotFreeText*>(d->pdfAnnot);
const AnnotCalloutLine *callout = ftextann->getCalloutLine();
if (!callout)
return QVector<QPointF>();
double MTX[6];
d->fillTransformationMTX(MTX);
const AnnotCalloutMultiLine * callout_v6 = dynamic_cast<const AnnotCalloutMultiLine*>(callout);
QVector<QPointF> res(callout_v6 ? 3 : 2);
XPDFReader::transform(MTX, callout->getX1(), callout->getY1(), res[0]);
XPDFReader::transform(MTX, callout->getX2(), callout->getY2(), res[1]);
if (callout_v6)
XPDFReader::transform(MTX, callout_v6->getX3(), callout_v6->getY3(), res[2]);
return res;
}
void TextAnnotation::setCalloutPoints( const QVector<QPointF> &points )
{
Q_D( TextAnnotation );
if (!d->pdfAnnot)
{
d->inplaceCallout = points;
return;
}
if (d->pdfAnnot->getType() != Annot::typeFreeText)
return;
AnnotFreeText * ftextann = static_cast<AnnotFreeText*>(d->pdfAnnot);
const int count = points.size();
if (count == 0)
{
ftextann->setCalloutLine(nullptr);
return;
}
if (count != 2 && count != 3)
{
error(errSyntaxError, -1, "Expected zero, two or three points for callout");
return;
}
AnnotCalloutLine *callout;
double x1, y1, x2, y2;
double MTX[6];
d->fillTransformationMTX(MTX);
XPDFReader::invTransform( MTX, points[0], x1, y1 );
XPDFReader::invTransform( MTX, points[1], x2, y2 );
if (count == 3)
{
double x3, y3;
XPDFReader::invTransform( MTX, points[2], x3, y3 );
callout = new AnnotCalloutMultiLine(x1, y1, x2, y2, x3, y3);
}
else
{
callout = new AnnotCalloutLine(x1, y1, x2, y2);
}
ftextann->setCalloutLine(callout);
delete callout;
}
TextAnnotation::InplaceIntent TextAnnotation::inplaceIntent() const
{
Q_D( const TextAnnotation );
if (!d->pdfAnnot)
return d->inplaceIntent;
if (d->pdfAnnot->getType() == Annot::typeFreeText)
{
const AnnotFreeText * ftextann = static_cast<const AnnotFreeText*>(d->pdfAnnot);
return (TextAnnotation::InplaceIntent)ftextann->getIntent();
}
return TextAnnotation::Unknown;
}
void TextAnnotation::setInplaceIntent( TextAnnotation::InplaceIntent intent )
{
Q_D( TextAnnotation );
if (!d->pdfAnnot)
{
d->inplaceIntent = intent;
return;
}
if (d->pdfAnnot->getType() == Annot::typeFreeText)
{
AnnotFreeText * ftextann = static_cast<AnnotFreeText*>(d->pdfAnnot);
ftextann->setIntent((AnnotFreeText::AnnotFreeTextIntent)intent);
}
}
/** LineAnnotation [Annotation] */
class LineAnnotationPrivate : public AnnotationPrivate
{
public:
LineAnnotationPrivate();
Annotation * makeAlias() override;
Annot* createNativeAnnot(::Page *destPage, DocumentData *doc) override;
// data fields (note uses border for rendering style)
QLinkedList<QPointF> linePoints;
LineAnnotation::TermStyle lineStartStyle;
LineAnnotation::TermStyle lineEndStyle;
bool lineClosed : 1; // (if true draw close shape)
bool lineShowCaption : 1;
LineAnnotation::LineType lineType;
QColor lineInnerColor;
double lineLeadingFwdPt;
double lineLeadingBackPt;
LineAnnotation::LineIntent lineIntent;
};
LineAnnotationPrivate::LineAnnotationPrivate()
: AnnotationPrivate(), lineStartStyle( LineAnnotation::None ),
lineEndStyle( LineAnnotation::None ), lineClosed( false ),
lineShowCaption( false ), lineLeadingFwdPt( 0 ),
lineLeadingBackPt( 0 ), lineIntent( LineAnnotation::Unknown )
{
}
Annotation * LineAnnotationPrivate::makeAlias()
{
return new LineAnnotation(*this);
}
Annot* LineAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
{
// Setters are defined in the public class
LineAnnotation *q = static_cast<LineAnnotation*>( makeAlias() );
// Set page and document
pdfPage = destPage;
parentDoc = doc;
// Set pdfAnnot
PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
if (lineType == LineAnnotation::StraightLine)
{
pdfAnnot = new AnnotLine(doc->doc, &rect);
}
else
{
pdfAnnot = new AnnotPolygon(doc->doc, &rect,
lineClosed ? Annot::typePolygon : Annot::typePolyLine );
}
// Set properties
flushBaseAnnotationProperties();
q->setLinePoints(linePoints);
q->setLineStartStyle(lineStartStyle);
q->setLineEndStyle(lineEndStyle);
q->setLineInnerColor(lineInnerColor);
q->setLineLeadingForwardPoint(lineLeadingFwdPt);
q->setLineLeadingBackPoint(lineLeadingBackPt);
q->setLineShowCaption(lineShowCaption);
q->setLineIntent(lineIntent);
delete q;
linePoints.clear(); // Free up memory
return pdfAnnot;
}
LineAnnotation::LineAnnotation( LineAnnotation::LineType type )
: Annotation( *new LineAnnotationPrivate() )
{
setLineType(type);
}
LineAnnotation::LineAnnotation(LineAnnotationPrivate &dd)
: Annotation( dd )
{}
LineAnnotation::LineAnnotation( const QDomNode & node )
: Annotation( *new LineAnnotationPrivate(), node )
{
// loop through the whole children looking for a 'line' element
QDomNode subNode = node.firstChild();
while( subNode.isElement() )
{
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
if ( e.tagName() != QLatin1String("line") )
continue;
// parse the attributes
if ( e.hasAttribute( QStringLiteral("startStyle") ) )
setLineStartStyle((LineAnnotation::TermStyle)e.attribute( QStringLiteral("startStyle") ).toInt());
if ( e.hasAttribute( QStringLiteral("endStyle") ) )
setLineEndStyle((LineAnnotation::TermStyle)e.attribute( QStringLiteral("endStyle") ).toInt());
if ( e.hasAttribute( QStringLiteral("closed") ) )
setLineClosed(e.attribute( QStringLiteral("closed") ).toInt());
if ( e.hasAttribute( QStringLiteral("innerColor") ) )
setLineInnerColor(QColor( e.attribute( QStringLiteral("innerColor") ) ));
if ( e.hasAttribute( QStringLiteral("leadFwd") ) )
setLineLeadingForwardPoint(e.attribute( QStringLiteral("leadFwd") ).toDouble());
if ( e.hasAttribute( QStringLiteral("leadBack") ) )
setLineLeadingBackPoint(e.attribute( QStringLiteral("leadBack") ).toDouble());
if ( e.hasAttribute( QStringLiteral("showCaption") ) )
setLineShowCaption(e.attribute( QStringLiteral("showCaption") ).toInt());
if ( e.hasAttribute( QStringLiteral("intent") ) )
setLineIntent((LineAnnotation::LineIntent)e.attribute( QStringLiteral("intent") ).toInt());
// parse all 'point' subnodes
QLinkedList<QPointF> points;
QDomNode pointNode = e.firstChild();
while ( pointNode.isElement() )
{
QDomElement pe = pointNode.toElement();
pointNode = pointNode.nextSibling();
if ( pe.tagName() != QLatin1String("point") )
continue;
QPointF p(pe.attribute( QStringLiteral("x"), QStringLiteral("0.0") ).toDouble(), pe.attribute( QStringLiteral("y"), QStringLiteral("0.0") ).toDouble());
points.append( p );
}
setLinePoints(points);
setLineType(points.size() == 2 ? StraightLine : Polyline);
// loading complete
break;
}
}
LineAnnotation::~LineAnnotation()
{
}
void LineAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
// store base annotation properties
storeBaseAnnotationProperties( node, document );
// create [line] element
QDomElement lineElement = document.createElement( QStringLiteral("line") );
node.appendChild( lineElement );
// store the attributes
if ( lineStartStyle() != None )
lineElement.setAttribute( QStringLiteral("startStyle"), (int)lineStartStyle() );
if ( lineEndStyle() != None )
lineElement.setAttribute( QStringLiteral("endStyle"), (int)lineEndStyle() );
if ( isLineClosed() )
lineElement.setAttribute( QStringLiteral("closed"), isLineClosed() );
if ( lineInnerColor().isValid() )
lineElement.setAttribute( QStringLiteral("innerColor"), lineInnerColor().name() );
if ( lineLeadingForwardPoint() != 0.0 )
lineElement.setAttribute( QStringLiteral("leadFwd"), QString::number( lineLeadingForwardPoint() ) );
if ( lineLeadingBackPoint() != 0.0 )
lineElement.setAttribute( QStringLiteral("leadBack"), QString::number( lineLeadingBackPoint() ) );
if ( lineShowCaption() )
lineElement.setAttribute( QStringLiteral("showCaption"), lineShowCaption() );
if ( lineIntent() != Unknown )
lineElement.setAttribute( QStringLiteral("intent"), lineIntent() );
// append the list of points
const QLinkedList<QPointF> points = linePoints();
if ( points.count() > 1 )
{
QLinkedList<QPointF>::const_iterator it = points.begin(), end = points.end();
while ( it != end )
{
const QPointF & p = *it;
QDomElement pElement = document.createElement( QStringLiteral("point") );
lineElement.appendChild( pElement );
pElement.setAttribute( QStringLiteral("x"), QString::number( p.x() ) );
pElement.setAttribute( QStringLiteral("y"), QString::number( p.y() ) );
++it;
}
}
}
Annotation::SubType LineAnnotation::subType() const
{
return ALine;
}
LineAnnotation::LineType LineAnnotation::lineType() const
{
Q_D( const LineAnnotation );
if (!d->pdfAnnot)
return d->lineType;
return (d->pdfAnnot->getType() == Annot::typeLine) ?
LineAnnotation::StraightLine : LineAnnotation::Polyline;
}
void LineAnnotation::setLineType( LineAnnotation::LineType type )
{
Q_D( LineAnnotation );
if (!d->pdfAnnot)
{
d->lineType = type;
return;
}
// Type cannot be changed if annotation is already tied
}
QLinkedList<QPointF> LineAnnotation::linePoints() const
{
Q_D( const LineAnnotation );
if (!d->pdfAnnot)
return d->linePoints;
double MTX[6];
d->fillTransformationMTX(MTX);
QLinkedList<QPointF> res;
if (d->pdfAnnot->getType() == Annot::typeLine)
{
const AnnotLine * lineann = static_cast<const AnnotLine*>(d->pdfAnnot);
QPointF p;
XPDFReader::transform(MTX, lineann->getX1(), lineann->getY1(), p);
res.append(p);
XPDFReader::transform(MTX, lineann->getX2(), lineann->getY2(), p);
res.append(p);
}
else
{
const AnnotPolygon * polyann = static_cast<const AnnotPolygon*>(d->pdfAnnot);
const AnnotPath * vertices = polyann->getVertices();
for (int i = 0; i < vertices->getCoordsLength(); ++i)
{
QPointF p;
XPDFReader::transform(MTX, vertices->getX(i), vertices->getY(i), p);
res.append(p);
}
}
return res;
}
void LineAnnotation::setLinePoints( const QLinkedList<QPointF> &points )
{
Q_D( LineAnnotation );
if (!d->pdfAnnot)
{
d->linePoints = points;
return;
}
if (d->pdfAnnot->getType() == Annot::typeLine)
{
AnnotLine *lineann = static_cast<AnnotLine*>(d->pdfAnnot);
if (points.size() != 2)
{
error(errSyntaxError, -1, "Expected two points for a straight line");
return;
}
double x1, y1, x2, y2;
double MTX[6];
d->fillTransformationMTX(MTX);
XPDFReader::invTransform( MTX, points.first(), x1, y1 );
XPDFReader::invTransform( MTX, points.last(), x2, y2 );
lineann->setVertices(x1, y1, x2, y2);
}
else
{
AnnotPolygon *polyann = static_cast<AnnotPolygon*>(d->pdfAnnot);
AnnotPath * p = d->toAnnotPath(points);
polyann->setVertices(p);
delete p;
}
}
LineAnnotation::TermStyle LineAnnotation::lineStartStyle() const
{
Q_D( const LineAnnotation );
if (!d->pdfAnnot)
return d->lineStartStyle;
if (d->pdfAnnot->getType() == Annot::typeLine)
{
const AnnotLine * lineann = static_cast<const AnnotLine*>(d->pdfAnnot);
return (LineAnnotation::TermStyle)lineann->getStartStyle();
}
else
{
const AnnotPolygon * polyann = static_cast<const AnnotPolygon*>(d->pdfAnnot);
return (LineAnnotation::TermStyle)polyann->getStartStyle();
}
}
void LineAnnotation::setLineStartStyle( LineAnnotation::TermStyle style )
{
Q_D( LineAnnotation );
if (!d->pdfAnnot)
{
d->lineStartStyle = style;
return;
}
if (d->pdfAnnot->getType() == Annot::typeLine)
{
AnnotLine *lineann = static_cast<AnnotLine*>(d->pdfAnnot);
lineann->setStartEndStyle((AnnotLineEndingStyle)style, lineann->getEndStyle());
}
else
{
AnnotPolygon *polyann = static_cast<AnnotPolygon*>(d->pdfAnnot);
polyann->setStartEndStyle((AnnotLineEndingStyle)style, polyann->getEndStyle());
}
}
LineAnnotation::TermStyle LineAnnotation::lineEndStyle() const
{
Q_D( const LineAnnotation );
if (!d->pdfAnnot)
return d->lineEndStyle;
if (d->pdfAnnot->getType() == Annot::typeLine)
{
const AnnotLine * lineann = static_cast<const AnnotLine*>(d->pdfAnnot);
return (LineAnnotation::TermStyle)lineann->getEndStyle();
}
else
{
const AnnotPolygon * polyann = static_cast<const AnnotPolygon*>(d->pdfAnnot);
return (LineAnnotation::TermStyle)polyann->getEndStyle();
}
}
void LineAnnotation::setLineEndStyle( LineAnnotation::TermStyle style )
{
Q_D( LineAnnotation );
if (!d->pdfAnnot)
{
d->lineEndStyle = style;
return;
}
if (d->pdfAnnot->getType() == Annot::typeLine)
{
AnnotLine *lineann = static_cast<AnnotLine*>(d->pdfAnnot);
lineann->setStartEndStyle(lineann->getStartStyle(), (AnnotLineEndingStyle)style);
}
else
{
AnnotPolygon *polyann = static_cast<AnnotPolygon*>(d->pdfAnnot);
polyann->setStartEndStyle(polyann->getStartStyle(), (AnnotLineEndingStyle)style);
}
}
bool LineAnnotation::isLineClosed() const
{
Q_D( const LineAnnotation );
if (!d->pdfAnnot)
return d->lineClosed;
return d->pdfAnnot->getType() == Annot::typePolygon;
}
void LineAnnotation::setLineClosed( bool closed )
{
Q_D( LineAnnotation );
if (!d->pdfAnnot)
{
d->lineClosed = closed;
return;
}
if (d->pdfAnnot->getType() != Annot::typeLine)
{
AnnotPolygon *polyann = static_cast<AnnotPolygon*>(d->pdfAnnot);
// Set new subtype and switch intent if necessary
if (closed)
{
polyann->setType(Annot::typePolygon);
if (polyann->getIntent() == AnnotPolygon::polylineDimension)
polyann->setIntent( AnnotPolygon::polygonDimension );
}
else
{
polyann->setType(Annot::typePolyLine);
if (polyann->getIntent() == AnnotPolygon::polygonDimension)
polyann->setIntent( AnnotPolygon::polylineDimension );
}
}
}
QColor LineAnnotation::lineInnerColor() const
{
Q_D( const LineAnnotation );
if (!d->pdfAnnot)
return d->lineInnerColor;
AnnotColor * c;
if (d->pdfAnnot->getType() == Annot::typeLine)
{
const AnnotLine * lineann = static_cast<const AnnotLine*>(d->pdfAnnot);
c = lineann->getInteriorColor();
}
else
{
const AnnotPolygon * polyann = static_cast<const AnnotPolygon*>(d->pdfAnnot);
c = polyann->getInteriorColor();
}
return convertAnnotColor(c);
}
void LineAnnotation::setLineInnerColor( const QColor &color )
{
Q_D( LineAnnotation );
if (!d->pdfAnnot)
{
d->lineInnerColor = color;
return;
}
auto c = convertQColor(color);
if (d->pdfAnnot->getType() == Annot::typeLine)
{
AnnotLine *lineann = static_cast<AnnotLine*>(d->pdfAnnot);
lineann->setInteriorColor(std::move(c));
}
else
{
AnnotPolygon *polyann = static_cast<AnnotPolygon*>(d->pdfAnnot);
polyann->setInteriorColor(std::move(c));
}
}
double LineAnnotation::lineLeadingForwardPoint() const
{
Q_D( const LineAnnotation );
if (!d->pdfAnnot)
return d->lineLeadingFwdPt;
if (d->pdfAnnot->getType() == Annot::typeLine)
{
const AnnotLine * lineann = static_cast<const AnnotLine*>(d->pdfAnnot);
return lineann->getLeaderLineLength();
}
return 0;
}
void LineAnnotation::setLineLeadingForwardPoint( double point )
{
Q_D( LineAnnotation );
if (!d->pdfAnnot)
{
d->lineLeadingFwdPt = point;
return;
}
if (d->pdfAnnot->getType() == Annot::typeLine)
{
AnnotLine *lineann = static_cast<AnnotLine*>(d->pdfAnnot);
lineann->setLeaderLineLength(point);
}
}
double LineAnnotation::lineLeadingBackPoint() const
{
Q_D( const LineAnnotation );
if (!d->pdfAnnot)
return d->lineLeadingBackPt;
if (d->pdfAnnot->getType() == Annot::typeLine)
{
const AnnotLine * lineann = static_cast<const AnnotLine*>(d->pdfAnnot);
return lineann->getLeaderLineExtension();
}
return 0;
}
void LineAnnotation::setLineLeadingBackPoint( double point )
{
Q_D( LineAnnotation );
if (!d->pdfAnnot)
{
d->lineLeadingBackPt = point;
return;
}
if (d->pdfAnnot->getType() == Annot::typeLine)
{
AnnotLine *lineann = static_cast<AnnotLine*>(d->pdfAnnot);
lineann->setLeaderLineExtension(point);