/***************************************************************************
 *   Copyright (C) 2005 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 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "annotations.h"
#include "annotations_p.h"

// qt/kde includes
#include <QApplication>
#include <QColor>

// DBL_MAX
#include <float.h>

// local includes
#include "action.h"
#include "document.h"
#include "document_p.h"
#include "movie.h"
#include "page_p.h"
#include "sound.h"

using namespace Okular;

/**
 * True, if point @p c lies to the left of the vector from @p a to @p b
 * @internal
 */
static bool isLeftOfVector( const NormalizedPoint& a, const NormalizedPoint& b, const NormalizedPoint& c )
{
    //cross product
    return ( (b.x - a.x) * ( c.y - a.y) - ( b.y - a.y ) * ( c.x - a.x ) ) > 0;
}

/**
 * @brief Calculates distance of the given point @p x @p y @p xScale @p yScale to the @p path
 *
 * Does piecewise comparison and selects the distance to the closest segment
 */
static double distanceSqr( double x, double y, double xScale, double yScale, const QLinkedList<NormalizedPoint>& path )
{
    double distance = DBL_MAX;
    double thisDistance;
    QLinkedList<NormalizedPoint>::const_iterator i = path.constBegin();
    NormalizedPoint lastPoint = *i;

    for (++i; i != path.constEnd(); ++i) {
        thisDistance = NormalizedPoint::distanceSqr( x, y, xScale, yScale, lastPoint, (*i) );

        if ( thisDistance < distance )
            distance = thisDistance;

        lastPoint = *i;
    }
    return distance;
}

/**
 * Given the squared @p distance from the idealized 0-width line and a pen width @p penWidth,
 * (not squared!), returns the final distance
 *
 * @warning The returned distance is not exact:
 * We calculate an (exact) squared distance to the ideal (centered) line, and then subtract
 * the squared width of the pen:
 * a^2 - b^2 where a = "distance from idealized 0-width line" b = "pen width"
 * For an exact result, we would want to calculate "(a - b)^2" but that would require
 * a square root operation because we only know the squared distance a^2.
 *
 * However, the approximation is feasible, because:
 * error = (a-b)^2 - (a^2 - b^2) = -2ab + 2b^2 = 2b(b - a)
 * Therefore:
 * lim_{a->b} a^2 - b^2 - a^2 + 2ab - b^2 --> 0
 *
 * In other words, this approximation will estimate the distance to be slightly more than it actually is
 * for as long as we are far "outside" the line, becoming more accurate the closer we get to the line
 * boundary. Trivially, it also fulfils (a1 < a2) => ((a1^2 - b^2) < (a2^2 - b^2)) making it monotonic.
 * "Inside" of the drawn line, the distance is 0 anyway.
 */
static double strokeDistance( double distance, double penWidth )
{
    return fmax(distance - pow( penWidth, 2 ), 0);
}

//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();
}

QRect AnnotationUtils::annotationGeometry( const Annotation * annotation,
    double scaleX, double scaleY )
{
    const QRect rect = annotation->transformedBoundingRectangle().geometry( (int)scaleX, (int)scaleY );
    if ( annotation->subType() == Annotation::AText && ( ( (TextAnnotation*)annotation )->textType() == TextAnnotation::Linked ) )
    {
        // To be honest i have no clue of why the 24,24 is here, maybe to make sure it's not too small?
        // But why only for linked text?
        const QRect rect24 = QRect( (int)( annotation->transformedBoundingRectangle().left * scaleX ),
                                    (int)( annotation->transformedBoundingRectangle().top * scaleY ), 24, 24 );
        return rect24.united(rect);
    }

    return rect;
}
//END AnnotationUtils implementation

AnnotationProxy::AnnotationProxy()
{
}

AnnotationProxy::~AnnotationProxy()
{
}

//BEGIN Annotation implementation

class Annotation::Style::Private
{
    public:
        Private()
            : m_opacity( 1.0 ), m_width( 1.0 ), m_style( Solid ), m_xCorners( 0.0 ),
              m_yCorners( 0.0 ), m_marks( 3 ), m_spaces( 0 ), m_effect( NoEffect ),
              m_effectIntensity( 1.0 )
        {
        }

        QColor m_color;
        double m_opacity;
        double m_width;
        LineStyle m_style;
        double m_xCorners;
        double m_yCorners;
        int m_marks;
        int m_spaces;
        LineEffect m_effect;
        double m_effectIntensity;
};

Annotation::Style::Style()
    : d( new Private )
{
}

Annotation::Style::~Style()
{
    delete d;
}

Annotation::Style::Style( const Style &other )
    : d( new Private )
{
    *d = *other.d;
}

Annotation::Style& Annotation::Style::operator=( const Style &other )
{
    if ( this != &other )
        *d = *other.d;

    return *this;
}

void Annotation::Style::setColor( const QColor &color )
{
    d->m_color = color;
}

QColor Annotation::Style::color() const
{
    return d->m_color;
}

void Annotation::Style::setOpacity( double opacity )
{
    d->m_opacity = opacity;
}

double Annotation::Style::opacity() const
{
    return d->m_opacity;
}

void Annotation::Style::setWidth( double width )
{
    d->m_width = width;
}

double Annotation::Style::width() const
{
    return d->m_width;
}

void Annotation::Style::setLineStyle( LineStyle style )
{
    d->m_style = style;
}

Annotation::LineStyle Annotation::Style::lineStyle() const
{
    return d->m_style;
}

void Annotation::Style::setXCorners( double xCorners )
{
    d->m_xCorners = xCorners;
}

double Annotation::Style::xCorners() const
{
    return d->m_xCorners;
}

void Annotation::Style::setYCorners( double yCorners )
{
    d->m_yCorners = yCorners;
}

double Annotation::Style::yCorners() const
{
    return d->m_yCorners;
}

void Annotation::Style::setMarks( int marks )
{
    d->m_marks = marks;
}

int Annotation::Style::marks() const
{
    return d->m_marks;
}

void Annotation::Style::setSpaces( int spaces )
{
    d->m_spaces = spaces;
}

int Annotation::Style::spaces() const
{
    return d->m_spaces;
}

void Annotation::Style::setLineEffect( LineEffect effect )
{
    d->m_effect = effect;
}

Annotation::LineEffect Annotation::Style::lineEffect() const
{
    return d->m_effect;
}

void Annotation::Style::setEffectIntensity( double intensity )
{
    d->m_effectIntensity = intensity;
}

double Annotation::Style::effectIntensity() const
{
    return d->m_effectIntensity;
}


class Annotation::Window::Private
{
    public:
        Private()
            : m_flags( -1 ), m_width( 0 ), m_height( 0 )
        {
        }

        int m_flags;
        NormalizedPoint m_topLeft;
        int m_width;
        int m_height;
        QString m_title;
        QString m_summary;
};

Annotation::Window::Window()
    : d( new Private )
{
}

Annotation::Window::~Window()
{
    delete d;
}

Annotation::Window::Window( const Window &other )
    : d( new Private )
{
    *d = *other.d;
}

Annotation::Window& Annotation::Window::operator=( const Window &other )
{
    if ( this != &other )
        *d = *other.d;

    return *this;
}

void Annotation::Window::setFlags( int flags )
{
    d->m_flags = flags;
}

int Annotation::Window::flags() const
{
    return d->m_flags;
}

void Annotation::Window::setTopLeft( const NormalizedPoint &point )
{
    d->m_topLeft = point;
}

NormalizedPoint Annotation::Window::topLeft() const
{
    return d->m_topLeft;
}

void Annotation::Window::setWidth( int width )
{
    d->m_width = width;
}

int Annotation::Window::width() const
{
    return d->m_width;
}

void Annotation::Window::setHeight( int height )
{
    d->m_height = height;
}

int Annotation::Window::height() const
{
    return d->m_height;
}

void Annotation::Window::setTitle( const QString &title )
{
    d->m_title = title;
}

QString Annotation::Window::title() const
{
    return d->m_title;
}

void Annotation::Window::setSummary( const QString &summary )
{
    d->m_summary = summary;
}

QString Annotation::Window::summary() const
{
    return d->m_summary;
}

class Annotation::Revision::Private
{
    public:
        Private()
            : m_annotation( nullptr ), m_scope( Reply ), m_type( None )
        {
        }

        Annotation *m_annotation;
        RevisionScope m_scope;
        RevisionType m_type;
};

Annotation::Revision::Revision()
    : d( new Private )
{
}

Annotation::Revision::~Revision()
{
    delete d;
}

Annotation::Revision::Revision( const Revision &other )
    : d( new Private )
{
    *d = *other.d;
}

Annotation::Revision& Annotation::Revision::operator=( const Revision &other )
{
    if ( this != &other )
        *d = *other.d;

    return *this;
}

void Annotation::Revision::setAnnotation( Annotation *annotation )
{
    d->m_annotation = annotation;
}

Annotation *Annotation::Revision::annotation() const
{
    return d->m_annotation;
}

void Annotation::Revision::setScope( RevisionScope scope )
{
    d->m_scope = scope;
}

Annotation::RevisionScope Annotation::Revision::scope() const
{
    return d->m_scope;
}

void Annotation::Revision::setType( RevisionType type )
{
    d->m_type = type;
}

Annotation::RevisionType Annotation::Revision::type() const
{
    return d->m_type;
}


AnnotationPrivate::AnnotationPrivate()
    : m_page( nullptr ), m_flags( 0 ), m_disposeFunc( nullptr )
{
}

AnnotationPrivate::~AnnotationPrivate()
{
    // delete all children revisions
    if ( m_revisions.isEmpty() )
        return;

    QLinkedList< Annotation::Revision >::iterator it = m_revisions.begin(), end = m_revisions.end();
    for ( ; it != end; ++it )
        delete (*it).annotation();
}

Annotation::Annotation( AnnotationPrivate &dd )
    : d_ptr( &dd )
{
}

Annotation::Annotation( AnnotationPrivate &dd, const QDomNode & description )
    : d_ptr( &dd )
{
    d_ptr->setAnnotationProperties( description );
}

Annotation::~Annotation()
{
    if ( d_ptr->m_disposeFunc )
        d_ptr->m_disposeFunc( this );

    delete d_ptr;
}

void Annotation::setAuthor( const QString &author )
{
    Q_D( Annotation );
    d->m_author = author;
}

QString Annotation::author() const
{
    Q_D( const Annotation );
    return d->m_author;
}

void Annotation::setContents( const QString &contents )
{
    Q_D( Annotation );
    d->m_contents = contents;
}

QString Annotation::contents() const
{
    Q_D( const Annotation );
    return d->m_contents;
}

void Annotation::setUniqueName( const QString &name )
{
    Q_D( Annotation );
    d->m_uniqueName = name;
}

QString Annotation::uniqueName() const
{
    Q_D( const Annotation );
    return d->m_uniqueName;
}

void Annotation::setModificationDate( const QDateTime &date )
{
    Q_D( Annotation );
    d->m_modifyDate = date;
}

QDateTime Annotation::modificationDate() const
{
    Q_D( const Annotation );
    return d->m_modifyDate;
}

void Annotation::setCreationDate( const QDateTime &date )
{
    Q_D( Annotation );
    d->m_creationDate = date;
}

QDateTime Annotation::creationDate() const
{
    Q_D( const Annotation );
    return d->m_creationDate;
}

void Annotation::setFlags( int flags )
{
    Q_D( Annotation );
    d->m_flags = flags;
}

int Annotation::flags() const
{
    Q_D( const Annotation );
    return d->m_flags;
}

void Annotation::setBoundingRectangle( const NormalizedRect &rectangle )
{
    Q_D( Annotation );
    d->m_boundary = rectangle;
    d->resetTransformation();
    if ( d->m_page )
    {
        d->transform( d->m_page->rotationMatrix() );
    }
}

NormalizedRect Annotation::boundingRectangle() const
{
    Q_D( const Annotation );
    return d->m_boundary;
}

NormalizedRect Annotation::transformedBoundingRectangle() const
{
    Q_D( const Annotation );
    return d->m_transformedBoundary;
}

void Annotation::translate( const NormalizedPoint &coord )
{
    Q_D( Annotation );
    d->translate( coord );
    d->resetTransformation();
    if ( d->m_page )
    {
        d->transform( d->m_page->rotationMatrix() );
    }
}

void Annotation::adjust( const NormalizedPoint & deltaCoord1, const NormalizedPoint & deltaCoord2 )
{
    Q_D( Annotation );
    d->adjust( deltaCoord1, deltaCoord2 );
    d->resetTransformation();
    if ( d->m_page )
    {
        d->transform( d->m_page->rotationMatrix() );
    }
}

bool Annotation::openDialogAfterCreation() const
{
    Q_D( const Annotation );
    return d->openDialogAfterCreation();
}

Annotation::Style & Annotation::style()
{
    Q_D( Annotation );
    return d->m_style;
}

const Annotation::Style & Annotation::style() const
{
    Q_D( const Annotation );
    return d->m_style;
}

Annotation::Window & Annotation::window()
{
    Q_D( Annotation );
    return d->m_window;
}

const Annotation::Window & Annotation::window() const
{
    Q_D( const Annotation );
    return d->m_window;
}

QLinkedList< Annotation::Revision > & Annotation::revisions()
{
    Q_D( Annotation );
    return d->m_revisions;
}

const QLinkedList< Annotation::Revision > & Annotation::revisions() const
{
    Q_D( const Annotation );
    return d->m_revisions;
}

void Annotation::setNativeId( const QVariant &id )
{
    Q_D( Annotation );
    d->m_nativeId = id;
}

QVariant Annotation::nativeId() const
{
    Q_D( const Annotation );
    return d->m_nativeId;
}

void Annotation::setDisposeDataFunction( DisposeDataFunction func )
{
    Q_D( Annotation );
    d->m_disposeFunc = func;
}

bool Annotation::canBeMoved() const
{
    Q_D( const Annotation );

    // Don't move annotations if they cannot be modified
    if ( !d->m_page || !d->m_page->m_doc->m_parent->canModifyPageAnnotation(this) )
        return false;

    // highlight "requires" to be "bounded" to text, and that's tricky for now
    if ( subType() == AHighlight )
        return false;

    return true;
}

bool Annotation::canBeResized() const
{
    Q_D( const Annotation );

    // Don't resize annotations if they cannot be modified
    if ( !d->m_page || !d->m_page->m_doc->m_parent->canModifyPageAnnotation(this) )
        return false;

    return d->canBeResized();
}

void Annotation::store( QDomNode & annNode, QDomDocument & document ) const
{
    Q_D( const Annotation );
    // create [base] element of the annotation node
    QDomElement e = document.createElement( QStringLiteral("base") );
    annNode.appendChild( e );

    // store -contents- attributes
    if ( !d->m_author.isEmpty() )
        e.setAttribute( QStringLiteral("author"), d->m_author );
    if ( !d->m_contents.isEmpty() )
        e.setAttribute( QStringLiteral("contents"), d->m_contents );
    if ( !d->m_uniqueName.isEmpty() )
        e.setAttribute( QStringLiteral("uniqueName"), d->m_uniqueName );
    if ( d->m_modifyDate.isValid() )
        e.setAttribute( QStringLiteral("modifyDate"), d->m_modifyDate.toString(Qt::ISODate) );
    if ( d->m_creationDate.isValid() )
        e.setAttribute( QStringLiteral("creationDate"), d->m_creationDate.toString(Qt::ISODate) );

    // store -other- attributes
    if ( d->m_flags ) // Strip internal flags
        e.setAttribute( QStringLiteral("flags"), d->m_flags & ~(External | ExternallyDrawn | BeingMoved | BeingResized ) );
    if ( d->m_style.color().isValid() )
        e.setAttribute( QStringLiteral("color"), d->m_style.color().name( QColor::HexArgb ) );
    if ( d->m_style.opacity() != 1.0 )
        e.setAttribute( QStringLiteral("opacity"), QString::number( d->m_style.opacity() ) );

    // Sub-Node-1 - boundary
    QDomElement bE = document.createElement( QStringLiteral("boundary") );
    e.appendChild( bE );
    bE.setAttribute( QStringLiteral("l"), QString::number( d->m_boundary.left ) );
    bE.setAttribute( QStringLiteral("t"), QString::number( d->m_boundary.top ) );
    bE.setAttribute( QStringLiteral("r"), QString::number( d->m_boundary.right ) );
    bE.setAttribute( QStringLiteral("b"), QString::number( d->m_boundary.bottom ) );

    // Sub-Node-2 - penStyle
    if ( d->m_style.width() != 1 || d->m_style.lineStyle() != Solid || d->m_style.xCorners() != 0 ||
         d->m_style.yCorners() != 0.0 || d->m_style.marks() != 3 || d->m_style.spaces() != 0 )
    {
        QDomElement psE = document.createElement( QStringLiteral("penStyle") );
        e.appendChild( psE );
        psE.setAttribute( QStringLiteral("width"), QString::number( d->m_style.width() ) );
        psE.setAttribute( QStringLiteral("style"), (int)d->m_style.lineStyle() );
        psE.setAttribute( QStringLiteral("xcr"), QString::number( d->m_style.xCorners() ) );
        psE.setAttribute( QStringLiteral("ycr"), QString::number( d->m_style.yCorners() ) );
        psE.setAttribute( QStringLiteral("marks"), d->m_style.marks() );
        psE.setAttribute( QStringLiteral("spaces"), d->m_style.spaces() );
    }

    // Sub-Node-3 - penEffect
    if ( d->m_style.lineEffect() != NoEffect || d->m_style.effectIntensity() != 1.0 )
    {
        QDomElement peE = document.createElement( QStringLiteral("penEffect") );
        e.appendChild( peE );
        peE.setAttribute( QStringLiteral("effect"), (int)d->m_style.lineEffect() );
        peE.setAttribute( QStringLiteral("intensity"), QString::number( d->m_style.effectIntensity() ) );
    }

    // Sub-Node-4 - window
    if ( d->m_window.flags() != -1 || !d->m_window.title().isEmpty() ||
         !d->m_window.summary().isEmpty() )
    {
        QDomElement wE = document.createElement( QStringLiteral("window") );
        e.appendChild( wE );
        wE.setAttribute( QStringLiteral("flags"), d->m_window.flags() );
        wE.setAttribute( QStringLiteral("top"), QString::number( d->m_window.topLeft().x ) );
        wE.setAttribute( QStringLiteral("left"), QString::number( d->m_window.topLeft().y ) );
        wE.setAttribute( QStringLiteral("width"), d->m_window.width() );
        wE.setAttribute( QStringLiteral("height"), d->m_window.height() );
        wE.setAttribute( QStringLiteral("title"), d->m_window.title() );
        wE.setAttribute( QStringLiteral("summary"), d->m_window.summary() );
    }

    // create [revision] element of the annotation node (if any)
    if ( d->m_revisions.isEmpty() )
        return;

    // add all revisions as children of revisions element
    QLinkedList< Revision >::const_iterator it = d->m_revisions.begin(), end = d->m_revisions.end();
    for ( ; it != end; ++it )
    {
        // create revision element
        const Revision & revision = *it;
        QDomElement r = document.createElement( QStringLiteral("revision") );
        annNode.appendChild( r );
        // set element attributes
        r.setAttribute( QStringLiteral("revScope"), (int)revision.scope() );
        r.setAttribute( QStringLiteral("revType"), (int)revision.type() );
        // use revision as the annotation element, so fill it up
        AnnotationUtils::storeAnnotation( revision.annotation(), r, document );
    }
}

QDomNode Annotation::getAnnotationPropertiesDomNode() const
{
    QDomDocument doc( QStringLiteral("documentInfo") );
    QDomElement node = doc.createElement( QStringLiteral("annotation") );

    store(node, doc);
    return node;
}

void Annotation::setAnnotationProperties( const QDomNode& node )
{
    // Save off internal properties that aren't contained in node
    Okular::PagePrivate *p = d_ptr->m_page;
    QVariant nativeID = d_ptr->m_nativeId;
    const int internalFlags = d_ptr->m_flags & (External | ExternallyDrawn | BeingMoved | BeingResized );
    Annotation::DisposeDataFunction disposeFunc = d_ptr->m_disposeFunc;

    // Replace AnnotationPrivate object with a fresh copy
    AnnotationPrivate *new_d_ptr = d_ptr->getNewAnnotationPrivate();
    delete( d_ptr );
    d_ptr = new_d_ptr;

    // Set the annotations properties from node
    d_ptr->setAnnotationProperties(node);

    // Restore internal properties
    d_ptr->m_page = p;
    d_ptr->m_nativeId = nativeID;
    d_ptr->m_flags = d_ptr->m_flags | internalFlags;
    d_ptr->m_disposeFunc = disposeFunc;

    // Transform annotation to current page rotation
    d_ptr->transform( d_ptr->m_page->rotationMatrix() );
}

double AnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) const
{
    return m_transformedBoundary.distanceSqr( x, y, xScale, yScale );
}

void AnnotationPrivate::annotationTransform( const QTransform &matrix )
{
    resetTransformation();
    transform( matrix );
}

void AnnotationPrivate::transform( const QTransform &matrix )
{
    m_transformedBoundary.transform( matrix );
}

void AnnotationPrivate::baseTransform( const QTransform &matrix )
{
    m_boundary.transform( matrix );
}

void AnnotationPrivate::resetTransformation()
{
    m_transformedBoundary = m_boundary;
}

void AnnotationPrivate::translate( const NormalizedPoint &coord )
{
    m_boundary.left = m_boundary.left + coord.x;
    m_boundary.right = m_boundary.right + coord.x;
    m_boundary.top = m_boundary.top + coord.y;
    m_boundary.bottom = m_boundary.bottom + coord.y;
}

void AnnotationPrivate::adjust( const NormalizedPoint &deltaCoord1, const NormalizedPoint &deltaCoord2 )
{
    m_boundary.left = m_boundary.left + qBound( -m_boundary.left, deltaCoord1.x, m_boundary.right - m_boundary.left );
    m_boundary.top = m_boundary.top + qBound( -m_boundary.top, deltaCoord1.y, m_boundary.bottom - m_boundary.top );;
    m_boundary.right = m_boundary.right + qBound( m_boundary.left - m_boundary.right, deltaCoord2.x, 1. - m_boundary.right );
    m_boundary.bottom = m_boundary.bottom + qBound( m_boundary.top - m_boundary.bottom, deltaCoord2.y, 1. - m_boundary.bottom );
}

bool AnnotationPrivate::openDialogAfterCreation() const
{
    return false;
}

void AnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    // get the [base] element of the annotation node
    QDomElement e = AnnotationUtils::findChildElement( node, QStringLiteral("base") );
    if ( e.isNull() )
        return;

    // parse -contents- attributes
    if ( e.hasAttribute( QStringLiteral("author") ) )
        m_author = e.attribute( QStringLiteral("author") );
    if ( e.hasAttribute( QStringLiteral("contents") ) )
        m_contents = e.attribute( QStringLiteral("contents") );
    if ( e.hasAttribute( QStringLiteral("uniqueName") ) )
        m_uniqueName = e.attribute( QStringLiteral("uniqueName") );
    if ( e.hasAttribute( QStringLiteral("modifyDate") ) )
        m_modifyDate = QDateTime::fromString( e.attribute(QStringLiteral("modifyDate")), Qt::ISODate );
    if ( e.hasAttribute( QStringLiteral("creationDate") ) )
        m_creationDate = QDateTime::fromString( e.attribute(QStringLiteral("creationDate")), Qt::ISODate );

    // parse -other- attributes
    if ( e.hasAttribute( QStringLiteral("flags") ) )
        m_flags = e.attribute( QStringLiteral("flags") ).toInt();
    if ( e.hasAttribute( QStringLiteral("color") ) )
        m_style.setColor( QColor( e.attribute( QStringLiteral("color") ) ) );
    if ( e.hasAttribute( QStringLiteral("opacity") ) )
        m_style.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") )
        {
            m_boundary=NormalizedRect(ee.attribute( QStringLiteral("l") ).toDouble(),
                ee.attribute( QStringLiteral("t") ).toDouble(),
                ee.attribute( QStringLiteral("r") ).toDouble(),
                ee.attribute( QStringLiteral("b") ).toDouble());
        }
        // parse penStyle if not default
        else if ( ee.tagName() == QLatin1String("penStyle") )
        {
            m_style.setWidth( ee.attribute( QStringLiteral("width") ).toDouble() );
            m_style.setLineStyle( (Annotation::LineStyle)ee.attribute( QStringLiteral("style") ).toInt() );
            m_style.setXCorners( ee.attribute( QStringLiteral("xcr") ).toDouble() );
            m_style.setYCorners( ee.attribute( QStringLiteral("ycr") ).toDouble() );
            m_style.setMarks( ee.attribute( QStringLiteral("marks") ).toInt() );
            m_style.setSpaces( ee.attribute( QStringLiteral("spaces") ).toInt() );
        }
        // parse effectStyle if not default
        else if ( ee.tagName() == QLatin1String("penEffect") )
        {
            m_style.setLineEffect( (Annotation::LineEffect)ee.attribute( QStringLiteral("effect") ).toInt() );
            m_style.setEffectIntensity( ee.attribute( QStringLiteral("intensity") ).toDouble() );
        }
        // parse window if present
        else if ( ee.tagName() == QLatin1String("window") )
        {
            m_window.setFlags( ee.attribute( QStringLiteral("flags") ).toInt() );
            m_window.setTopLeft( NormalizedPoint( ee.attribute( QStringLiteral("top") ).toDouble(),
                                                  ee.attribute( QStringLiteral("left") ).toDouble() ) );
            m_window.setWidth( ee.attribute( QStringLiteral("width") ).toInt() );
            m_window.setHeight( ee.attribute( QStringLiteral("height") ).toInt() );
            m_window.setTitle( ee.attribute( QStringLiteral("title") ) );
            m_window.setSummary( ee.attribute( QStringLiteral("summary") ) );
        }
    }

    // get the [revisions] element of the annotation node
    QDomNode revNode = node.firstChild();
    for ( ; revNode.isElement(); revNode = revNode.nextSibling() )
    {
        QDomElement revElement = revNode.toElement();
        if ( revElement.tagName() != QLatin1String("revision") )
            continue;

        // compile the Revision structure crating annotation
        Annotation::Revision revision;
        revision.setScope( (Annotation::RevisionScope)revElement.attribute( QStringLiteral("revScope") ).toInt() );
        revision.setType( (Annotation::RevisionType)revElement.attribute( QStringLiteral("revType") ).toInt() );
        revision.setAnnotation( AnnotationUtils::createAnnotation( revElement ) );

        // if annotation is valid, add revision to internal list
        if ( revision.annotation() )
            m_revisions.append( revision );
    }

    m_transformedBoundary = m_boundary;
}

bool AnnotationPrivate::canBeResized() const
{
    return false;
}

//END Annotation implementation


/** TextAnnotation [Annotation] */

class Okular::TextAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        TextAnnotationPrivate()
            : AnnotationPrivate(), m_textType( TextAnnotation::Linked ),
              m_textIcon( QStringLiteral("Comment") ), m_inplaceAlign( 0 ),
              m_inplaceIntent( TextAnnotation::Unknown )
        {
        }

        void transform( const QTransform &matrix ) override;
        void baseTransform( const QTransform &matrix ) override;
        void resetTransformation() override;
        void translate( const NormalizedPoint &coord ) override;
        bool openDialogAfterCreation() const override;
        void setAnnotationProperties( const QDomNode& node ) override;
        bool canBeResized() const override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        TextAnnotation::TextType m_textType;
        QString m_textIcon;
        QFont m_textFont;
        QColor m_textColor;
        int m_inplaceAlign;
        NormalizedPoint m_inplaceCallout[3];
        NormalizedPoint m_transformedInplaceCallout[3];
        TextAnnotation::InplaceIntent m_inplaceIntent;
};

/*
  The default textIcon for text annotation is Note as the PDF Reference says
*/
TextAnnotation::TextAnnotation()
    : Annotation( *new TextAnnotationPrivate() )
{
}

TextAnnotation::TextAnnotation( const QDomNode & description )
    : Annotation( *new TextAnnotationPrivate(), description )
{
}

TextAnnotation::~TextAnnotation()
{
}

void TextAnnotation::setTextType( TextType textType )
{
    Q_D( TextAnnotation );
    d->m_textType = textType;
}

TextAnnotation::TextType TextAnnotation::textType() const
{
    Q_D( const TextAnnotation );
    return d->m_textType;
}

void TextAnnotation::setTextIcon( const QString &icon )
{
    Q_D( TextAnnotation );
    d->m_textIcon = icon;
}

QString TextAnnotation::textIcon() const
{
    Q_D( const TextAnnotation );
    return d->m_textIcon;
}

void TextAnnotation::setTextFont( const QFont &font )
{
    Q_D( TextAnnotation );
    d->m_textFont = font;
}

QFont TextAnnotation::textFont() const
{
    Q_D( const TextAnnotation );
    return d->m_textFont;
}

void TextAnnotation::setTextColor( const QColor &color )
{
	Q_D( TextAnnotation );
	d->m_textColor = color;
}

QColor TextAnnotation::textColor() const
{
	Q_D( const TextAnnotation );
	return d->m_textColor;
}

void TextAnnotation::setInplaceAlignment( int alignment )
{
    Q_D( TextAnnotation );
    d->m_inplaceAlign = alignment;
}

int TextAnnotation::inplaceAlignment() const
{
    Q_D( const TextAnnotation );
    return d->m_inplaceAlign;
}

void TextAnnotation::setInplaceCallout( const NormalizedPoint &point, int index )
{
    if ( index < 0 || index > 2 )
        return;

    Q_D( TextAnnotation );
    d->m_inplaceCallout[ index ] = point;
}

NormalizedPoint TextAnnotation::inplaceCallout( int index ) const
{
    if ( index < 0 || index > 2 )
        return NormalizedPoint();

    Q_D( const TextAnnotation );
    return d->m_inplaceCallout[ index ];
}

NormalizedPoint TextAnnotation::transformedInplaceCallout( int index ) const
{
    if ( index < 0 || index > 2 )
        return NormalizedPoint();

    Q_D( const TextAnnotation );
    return d->m_transformedInplaceCallout[ index ];
}

void TextAnnotation::setInplaceIntent( InplaceIntent intent )
{
    Q_D( TextAnnotation );
    d->m_inplaceIntent = intent;
}

TextAnnotation::InplaceIntent TextAnnotation::inplaceIntent() const
{
    Q_D( const TextAnnotation );
    return d->m_inplaceIntent;
}

Annotation::SubType TextAnnotation::subType() const
{
    return AText;
}

void TextAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    Q_D( const TextAnnotation );
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [text] element
    QDomElement textElement = document.createElement( QStringLiteral("text") );
    node.appendChild( textElement );

    // store the optional attributes
    if ( d->m_textType != Linked )
        textElement.setAttribute( QStringLiteral("type"), (int)d->m_textType );
    if ( !d->m_textIcon.isEmpty() )
        textElement.setAttribute( QStringLiteral("icon"), d->m_textIcon );
    if ( d->m_textFont != QApplication::font() )
        textElement.setAttribute( QStringLiteral("font"), d->m_textFont.toString() );
    if ( d->m_textColor.isValid() )
        textElement.setAttribute( QStringLiteral("fontColor"), d->m_textColor.name() );
    if ( d->m_inplaceAlign )
        textElement.setAttribute( QStringLiteral("align"), d->m_inplaceAlign );
    if ( d->m_inplaceIntent != Unknown )
        textElement.setAttribute( QStringLiteral("intent"), (int)d->m_inplaceIntent );

    // Sub-Node - callout
    if ( d->m_inplaceCallout[0].x != 0.0 )
    {
        QDomElement calloutElement = document.createElement( QStringLiteral("callout") );
        textElement.appendChild( calloutElement );
        calloutElement.setAttribute( QStringLiteral("ax"), QString::number( d->m_inplaceCallout[0].x ) );
        calloutElement.setAttribute( QStringLiteral("ay"), QString::number( d->m_inplaceCallout[0].y ) );
        calloutElement.setAttribute( QStringLiteral("bx"), QString::number( d->m_inplaceCallout[1].x ) );
        calloutElement.setAttribute( QStringLiteral("by"), QString::number( d->m_inplaceCallout[1].y ) );
        calloutElement.setAttribute( QStringLiteral("cx"), QString::number( d->m_inplaceCallout[2].x ) );
        calloutElement.setAttribute( QStringLiteral("cy"), QString::number( d->m_inplaceCallout[2].y ) );
    }
}

void TextAnnotationPrivate::transform( const QTransform &matrix )
{
    AnnotationPrivate::transform( matrix );

    for ( NormalizedPoint &np : m_transformedInplaceCallout ) {
       np.transform( matrix );
    }
}

void TextAnnotationPrivate::baseTransform( const QTransform &matrix )
{
    AnnotationPrivate::baseTransform( matrix );

    for ( NormalizedPoint &np : m_inplaceCallout ) {
       np.transform( matrix );
    }
}

void TextAnnotationPrivate::resetTransformation()
{
    AnnotationPrivate::resetTransformation();

    for ( int i = 0; i < 3; ++i ) {
       m_transformedInplaceCallout[i] = m_inplaceCallout[i];
    }
}

void TextAnnotationPrivate::translate( const NormalizedPoint &coord )
{
    AnnotationPrivate::translate( coord );

#define ADD_COORD( c1, c2 ) \
{ \
  c1.x = c1.x + c2.x; \
  c1.y = c1.y + c2.y; \
}
    ADD_COORD( m_inplaceCallout[0], coord )
    ADD_COORD( m_inplaceCallout[1], coord )
    ADD_COORD( m_inplaceCallout[2], coord )
#undef ADD_COORD
}

bool TextAnnotationPrivate::openDialogAfterCreation() const
{
    return ( m_textType == Okular::TextAnnotation::Linked );
}

void TextAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(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") ) )
            m_textType = (TextAnnotation::TextType)e.attribute( QStringLiteral("type") ).toInt();
        if ( e.hasAttribute( QStringLiteral("icon") ) )
            m_textIcon = e.attribute( QStringLiteral("icon") );
        if ( e.hasAttribute( QStringLiteral("font") ) )
            m_textFont.fromString( e.attribute( QStringLiteral("font") ) );
        if ( e.hasAttribute( QStringLiteral("fontColor") ) )
            m_textColor = QColor( e.attribute( QStringLiteral("fontColor") ) );
        if ( e.hasAttribute( QStringLiteral("align") ) )
            m_inplaceAlign = e.attribute( QStringLiteral("align") ).toInt();
        if ( e.hasAttribute( QStringLiteral("intent") ) )
            m_inplaceIntent = (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") )
            {
                m_contents = ee.firstChild().toCDATASection().data();
            }
            else if ( ee.tagName() == QLatin1String("callout") )
            {
                m_inplaceCallout[0].x = ee.attribute( QStringLiteral("ax") ).toDouble();
                m_inplaceCallout[0].y = ee.attribute( QStringLiteral("ay") ).toDouble();
                m_inplaceCallout[1].x = ee.attribute( QStringLiteral("bx") ).toDouble();
                m_inplaceCallout[1].y = ee.attribute( QStringLiteral("by") ).toDouble();
                m_inplaceCallout[2].x = ee.attribute( QStringLiteral("cx") ).toDouble();
                m_inplaceCallout[2].y = ee.attribute( QStringLiteral("cy") ).toDouble();
            }
        }

        // loading complete
        break;
    }

    for ( int i = 0; i < 3; ++i )
        m_transformedInplaceCallout[i] = m_inplaceCallout[i];
}

bool TextAnnotationPrivate::canBeResized() const
{
    if ( m_textType != TextAnnotation::Linked ) {
        return true;
    }
    return false;
}

AnnotationPrivate* TextAnnotationPrivate::getNewAnnotationPrivate()
{
    return new TextAnnotationPrivate();
}

/** LineAnnotation [Annotation] */

class Okular::LineAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        LineAnnotationPrivate()
            : AnnotationPrivate(),
              m_lineStartStyle( LineAnnotation::None ), m_lineEndStyle( LineAnnotation::None ),
              m_lineClosed( false ), m_lineShowCaption( false ), m_lineLeadingFwdPt( 0 ),
              m_lineLeadingBackPt( 0 ), m_lineIntent( LineAnnotation::Unknown )
        {
        }

        void transform( const QTransform &matrix ) override;
        void baseTransform( const QTransform &matrix ) override;
        void resetTransformation() override;
        void translate( const NormalizedPoint &coord ) override;
        double distanceSqr( double x, double y, double xScale, double yScale ) const override;
        void setAnnotationProperties( const QDomNode& node ) override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        QLinkedList<NormalizedPoint> m_linePoints;
        QLinkedList<NormalizedPoint> m_transformedLinePoints;
        LineAnnotation::TermStyle m_lineStartStyle;
        LineAnnotation::TermStyle m_lineEndStyle;
        bool m_lineClosed : 1;
        bool m_lineShowCaption : 1;
        QColor m_lineInnerColor;
        double m_lineLeadingFwdPt;
        double m_lineLeadingBackPt;
        LineAnnotation::LineIntent m_lineIntent;
};

LineAnnotation::LineAnnotation()
    : Annotation( *new LineAnnotationPrivate() )
{
}

LineAnnotation::LineAnnotation( const QDomNode & description )
    : Annotation( *new LineAnnotationPrivate(), description )
{
}

LineAnnotation::~LineAnnotation()
{
}

void LineAnnotation::setLinePoints( const QLinkedList<NormalizedPoint> &points )
{
    Q_D( LineAnnotation );
    d->m_linePoints = points;
}

QLinkedList<NormalizedPoint> LineAnnotation::linePoints() const
{
    Q_D( const LineAnnotation );
    return d->m_linePoints;
}

QLinkedList<NormalizedPoint> LineAnnotation::transformedLinePoints() const
{
    Q_D( const LineAnnotation );
    return d->m_transformedLinePoints;
}

void LineAnnotation::setLineStartStyle( TermStyle style )
{
    Q_D( LineAnnotation );
    d->m_lineStartStyle = style;
}

LineAnnotation::TermStyle LineAnnotation::lineStartStyle() const
{
    Q_D( const LineAnnotation );
    return d->m_lineStartStyle;
}

void LineAnnotation::setLineEndStyle( TermStyle style )
{
    Q_D( LineAnnotation );
    d->m_lineEndStyle = style;
}

LineAnnotation::TermStyle LineAnnotation::lineEndStyle() const
{
    Q_D( const LineAnnotation );
    return d->m_lineEndStyle;
}

void LineAnnotation::setLineClosed( bool closed )
{
    Q_D( LineAnnotation );
    d->m_lineClosed = closed;
}

bool LineAnnotation::lineClosed() const
{
    Q_D( const LineAnnotation );
    return d->m_lineClosed;
}

void LineAnnotation::setLineInnerColor( const QColor &color )
{
    Q_D( LineAnnotation );
    d->m_lineInnerColor = color;
}

QColor LineAnnotation::lineInnerColor() const
{
    Q_D( const LineAnnotation );
    return d->m_lineInnerColor;
}

void LineAnnotation::setLineLeadingForwardPoint( double point )
{
    Q_D( LineAnnotation );
    d->m_lineLeadingFwdPt = point;
}

double LineAnnotation::lineLeadingForwardPoint() const
{
    Q_D( const LineAnnotation );
    return d->m_lineLeadingFwdPt;
}

void LineAnnotation::setLineLeadingBackwardPoint( double point )
{
    Q_D( LineAnnotation );
    d->m_lineLeadingBackPt = point;
}

double LineAnnotation::lineLeadingBackwardPoint() const
{
    Q_D( const LineAnnotation );
    return d->m_lineLeadingBackPt;
}

void LineAnnotation::setShowCaption( bool show )
{
    Q_D( LineAnnotation );
    d->m_lineShowCaption = show;
}

bool LineAnnotation::showCaption() const
{
    Q_D( const LineAnnotation );
    return d->m_lineShowCaption;
}

void LineAnnotation::setLineIntent( LineIntent intent )
{
    Q_D( LineAnnotation );
    d->m_lineIntent = intent;
}

LineAnnotation::LineIntent LineAnnotation::lineIntent() const
{
    Q_D( const LineAnnotation );
    return d->m_lineIntent;
}

Annotation::SubType LineAnnotation::subType() const
{
    return ALine;
}

void LineAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    Q_D( const LineAnnotation );
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [line] element
    QDomElement lineElement = document.createElement( QStringLiteral("line") );
    node.appendChild( lineElement );

    // store the attributes
    if ( d->m_lineStartStyle != None )
        lineElement.setAttribute( QStringLiteral("startStyle"), (int)d->m_lineStartStyle );
    if ( d->m_lineEndStyle != None )
        lineElement.setAttribute( QStringLiteral("endStyle"), (int)d->m_lineEndStyle );
    if ( d->m_lineClosed )
        lineElement.setAttribute( QStringLiteral("closed"), d->m_lineClosed );
    if ( d->m_lineInnerColor.isValid() )
        lineElement.setAttribute( QStringLiteral("innerColor"), d->m_lineInnerColor.name() );
    if ( d->m_lineLeadingFwdPt != 0.0 )
        lineElement.setAttribute( QStringLiteral("leadFwd"), QString::number( d->m_lineLeadingFwdPt ) );
    if ( d->m_lineLeadingBackPt != 0.0 )
        lineElement.setAttribute( QStringLiteral("leadBack"), QString::number( d->m_lineLeadingBackPt ) );
    if ( d->m_lineShowCaption )
        lineElement.setAttribute( QStringLiteral("showCaption"), d->m_lineShowCaption );
    if ( d->m_lineIntent != Unknown )
        lineElement.setAttribute( QStringLiteral("intent"), d->m_lineIntent );

    // append the list of points
    int points = d->m_linePoints.count();
    if ( points > 1 )
    {
        QLinkedList<NormalizedPoint>::const_iterator it = d->m_linePoints.begin(), end = d->m_linePoints.end();
        while ( it != end )
        {
            const NormalizedPoint & 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++; //to avoid loop
        }
    }
}

void LineAnnotationPrivate::transform( const QTransform &matrix )
{
    AnnotationPrivate::transform( matrix );

    QMutableLinkedListIterator<NormalizedPoint> it( m_transformedLinePoints );
    while ( it.hasNext() )
        it.next().transform( matrix );
}

void LineAnnotationPrivate::baseTransform( const QTransform &matrix )
{
    AnnotationPrivate::baseTransform( matrix );

    QMutableLinkedListIterator<NormalizedPoint> it( m_linePoints );
    while ( it.hasNext() )
        it.next().transform( matrix );
}

void LineAnnotationPrivate::resetTransformation()
{
    AnnotationPrivate::resetTransformation();

    m_transformedLinePoints = m_linePoints;
}

void LineAnnotationPrivate::translate( const NormalizedPoint &coord )
{
    AnnotationPrivate::translate( coord );

    QMutableLinkedListIterator<NormalizedPoint> it( m_linePoints );
    while ( it.hasNext() )
    {
        NormalizedPoint& p = it.next();
        p.x = p.x + coord.x;
        p.y = p.y + coord.y;
    }
}

void LineAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(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") ) )
            m_lineStartStyle = (LineAnnotation::TermStyle)e.attribute( QStringLiteral("startStyle") ).toInt();
        if ( e.hasAttribute( QStringLiteral("endStyle") ) )
            m_lineEndStyle = (LineAnnotation::TermStyle)e.attribute( QStringLiteral("endStyle") ).toInt();
        if ( e.hasAttribute( QStringLiteral("closed") ) )
            m_lineClosed = e.attribute( QStringLiteral("closed") ).toInt();
        if ( e.hasAttribute( QStringLiteral("innerColor") ) )
            m_lineInnerColor = QColor( e.attribute( QStringLiteral("innerColor") ) );
        if ( e.hasAttribute( QStringLiteral("leadFwd") ) )
            m_lineLeadingFwdPt = e.attribute( QStringLiteral("leadFwd") ).toDouble();
        if ( e.hasAttribute( QStringLiteral("leadBack") ) )
            m_lineLeadingBackPt = e.attribute( QStringLiteral("leadBack") ).toDouble();
        if ( e.hasAttribute( QStringLiteral("showCaption") ) )
            m_lineShowCaption = e.attribute( QStringLiteral("showCaption") ).toInt();
        if ( e.hasAttribute( QStringLiteral("intent") ) )
            m_lineIntent = (LineAnnotation::LineIntent)e.attribute( QStringLiteral("intent") ).toInt();

        // parse all 'point' subnodes
        QDomNode pointNode = e.firstChild();
        while ( pointNode.isElement() )
        {
            QDomElement pe = pointNode.toElement();
            pointNode = pointNode.nextSibling();

            if ( pe.tagName() != QLatin1String("point") )
                continue;

            NormalizedPoint p;
            p.x = pe.attribute( QStringLiteral("x"), QStringLiteral("0.0") ).toDouble();
            p.y = pe.attribute( QStringLiteral("y"), QStringLiteral("0.0") ).toDouble();
            m_linePoints.append( p );
        }

        // loading complete
        break;
    }

    m_transformedLinePoints = m_linePoints;
}

AnnotationPrivate* LineAnnotationPrivate::getNewAnnotationPrivate()
{
    return new LineAnnotationPrivate();
}

double LineAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) const
{
    QLinkedList<NormalizedPoint> transformedLinePoints = m_transformedLinePoints;

    if ( m_lineClosed ) // Close the path
        transformedLinePoints.append( transformedLinePoints.first() );

    if ( m_lineInnerColor.isValid() )
    {
        QPolygonF polygon;
        for ( const NormalizedPoint &p : qAsConst(transformedLinePoints) )
            polygon.append( QPointF( p.x, p.y ) );

        if ( polygon.containsPoint( QPointF( x, y ), Qt::WindingFill ) )
            return 0;
    }

    return strokeDistance( ::distanceSqr( x, y, xScale, yScale, transformedLinePoints ),
                           m_style.width() * xScale / ( m_page->m_width * 2 ) );
}

/** GeomAnnotation [Annotation] */

class Okular::GeomAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        GeomAnnotationPrivate()
            : AnnotationPrivate(), m_geomType( GeomAnnotation::InscribedSquare )
        {
        }
        void setAnnotationProperties( const QDomNode& node ) override;
        bool canBeResized() const override;
        AnnotationPrivate* getNewAnnotationPrivate() override;
        double distanceSqr( double x, double y, double xScale, double yScale ) const override;

        GeomAnnotation::GeomType m_geomType;
        QColor m_geomInnerColor;
};

GeomAnnotation::GeomAnnotation()
    : Annotation( *new GeomAnnotationPrivate() )
{
}

GeomAnnotation::GeomAnnotation( const QDomNode & description )
    : Annotation( *new GeomAnnotationPrivate(), description )
{
}

GeomAnnotation::~GeomAnnotation()
{
}

void GeomAnnotation::setGeometricalType( GeomType type )
{
    Q_D( GeomAnnotation );
    d->m_geomType = type;
}

GeomAnnotation::GeomType GeomAnnotation::geometricalType() const
{
    Q_D( const GeomAnnotation );
    return d->m_geomType;
}

void GeomAnnotation::setGeometricalInnerColor( const QColor &color )
{
    Q_D( GeomAnnotation );
    d->m_geomInnerColor = color;
}

QColor GeomAnnotation::geometricalInnerColor() const
{
    Q_D( const GeomAnnotation );
    return d->m_geomInnerColor;
}

Annotation::SubType GeomAnnotation::subType() const
{
    return AGeom;
}

void GeomAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    Q_D( const GeomAnnotation );
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [geom] element
    QDomElement geomElement = document.createElement( QStringLiteral("geom") );
    node.appendChild( geomElement );

    // append the optional attributes
    if ( d->m_geomType != InscribedSquare )
        geomElement.setAttribute( QStringLiteral("type"), (int)d->m_geomType );
    if ( d->m_geomInnerColor.isValid() )
        geomElement.setAttribute( QStringLiteral("color"), d->m_geomInnerColor.name() );
}

void GeomAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(node);
    // loop through the whole children looking for a 'geom' element
    QDomNode subNode = node.firstChild();
    while( subNode.isElement() )
    {
        QDomElement e = subNode.toElement();
        subNode = subNode.nextSibling();
        if ( e.tagName() != QLatin1String("geom") )
            continue;

        // parse the attributes
        if ( e.hasAttribute( QStringLiteral("type") ) )
            m_geomType = (GeomAnnotation::GeomType)e.attribute( QStringLiteral("type") ).toInt();
        if ( e.hasAttribute( QStringLiteral("color") ) )
            m_geomInnerColor = QColor( e.attribute( QStringLiteral("color") ) );
        // compatibility
        if ( e.hasAttribute( QStringLiteral("width") ) )
            m_style.setWidth( e.attribute( QStringLiteral("width") ).toInt() );

        // loading complete
        break;
    }
}

bool GeomAnnotationPrivate::canBeResized() const
{
    return true;
}

AnnotationPrivate* GeomAnnotationPrivate::getNewAnnotationPrivate()
{
    return new GeomAnnotationPrivate();
}

double GeomAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) const
{
    double distance = 0;
    //the line thickness is applied unevenly (only on the "inside") - account for this
    bool withinShape = false;
    switch (m_geomType) {
        case GeomAnnotation::InscribedCircle:
        {
            //calculate the center point and focus lengths of the ellipse
            const double centerX = ( m_transformedBoundary.left + m_transformedBoundary.right ) / 2.0;
            const double centerY = ( m_transformedBoundary.top + m_transformedBoundary.bottom ) / 2.0;
            const double focusX = ( m_transformedBoundary.right - centerX);
            const double focusY = ( m_transformedBoundary.bottom - centerY);

            const double focusXSqr = pow( focusX, 2 );
            const double focusYSqr = pow( focusY, 2 );

            // to calculate the distance from the ellipse, we will first find the point "projection"
            // that lies on the ellipse and is closest to the point (x,y)
            // This point can obviously be written as "center + lambda(inputPoint - center)".
            // Because the point lies on the ellipse, we know that:
            //   1 = ((center.x - projection.x)/focusX)^2 + ((center.y - projection.y)/focusY)^2
            // After filling in projection.x = center.x + lambda * (inputPoint.x - center.x)
            // and its y-equivalent, we can solve for lambda:
            const double lambda = sqrt( focusXSqr * focusYSqr /
                                 ( focusYSqr * pow( x - centerX, 2 ) + focusXSqr * pow( y - centerY, 2 ) ) );

            // if the ellipse is filled, we treat all points within as "on" it
            if ( lambda > 1 )
            {
                if ( m_geomInnerColor.isValid() )
                    return 0;
                else
                    withinShape = true;
            }

            //otherwise we calculate the squared distance from the projected point on the ellipse
            NormalizedPoint projection( centerX, centerY );
            projection.x += lambda * ( x - centerX );
            projection.y += lambda * ( y - centerY );

            distance = projection.distanceSqr( x, y, xScale, yScale );
            break;
        }

        case GeomAnnotation::InscribedSquare:
            //if the square is filled, only check the bounding box
            if ( m_geomInnerColor.isValid() )
                return AnnotationPrivate::distanceSqr( x, y, xScale, yScale );

            QLinkedList<NormalizedPoint> edges;
            edges << NormalizedPoint( m_transformedBoundary.left, m_transformedBoundary.top );
            edges << NormalizedPoint( m_transformedBoundary.right, m_transformedBoundary.top );
            edges << NormalizedPoint( m_transformedBoundary.right, m_transformedBoundary.bottom );
            edges << NormalizedPoint( m_transformedBoundary.left, m_transformedBoundary.bottom );
            edges << NormalizedPoint( m_transformedBoundary.left, m_transformedBoundary.top );
            distance = ::distanceSqr( x, y, xScale, yScale, edges );

            if ( m_transformedBoundary.contains( x, y ) )
                withinShape = true;

            break;
    }
    if ( withinShape )
        distance = strokeDistance( distance, m_style.width() * xScale / m_page->m_width );

    return distance;
}

/** HighlightAnnotation [Annotation] */

class HighlightAnnotation::Quad::Private
{
    public:
        Private()
        {
        }

        NormalizedPoint m_points[4];
        NormalizedPoint m_transformedPoints[4];
        bool m_capStart : 1;
        bool m_capEnd : 1;
        double m_feather;
};

HighlightAnnotation::Quad::Quad()
    : d( new Private )
{
}

HighlightAnnotation::Quad::~Quad()
{
    delete d;
}

HighlightAnnotation::Quad::Quad( const Quad &other )
    : d( new Private )
{
    *d = *other.d;
}

HighlightAnnotation::Quad& HighlightAnnotation::Quad::operator=( const Quad &other )
{
    if ( this != &other )
        *d = *other.d;

    return *this;
}

void HighlightAnnotation::Quad::setPoint( const NormalizedPoint &point, int index )
{
    if ( index < 0 || index > 3 )
        return;

    d->m_points[ index ] = point;
}

NormalizedPoint HighlightAnnotation::Quad::point( int index ) const
{
    if ( index < 0 || index > 3 )
        return NormalizedPoint();

    return d->m_points[ index ];
}

NormalizedPoint HighlightAnnotation::Quad::transformedPoint( int index ) const
{
    if ( index < 0 || index > 3 )
        return NormalizedPoint();

    return d->m_transformedPoints[ index ];
}

void HighlightAnnotation::Quad::setCapStart( bool value )
{
    d->m_capStart = value;
}

bool HighlightAnnotation::Quad::capStart() const
{
    return d->m_capStart;
}

void HighlightAnnotation::Quad::setCapEnd( bool value )
{
    d->m_capEnd = value;
}

bool HighlightAnnotation::Quad::capEnd() const
{
    return d->m_capEnd;
}

void HighlightAnnotation::Quad::setFeather( double width )
{
    d->m_feather = width;
}

double HighlightAnnotation::Quad::feather() const
{
    return d->m_feather;
}

void HighlightAnnotation::Quad::transform( const QTransform &matrix )
{
    for ( int i = 0; i < 4; ++i ) {
        d->m_transformedPoints[ i ] = d->m_points[ i ];
        d->m_transformedPoints[ i ].transform( matrix );
    }
}


class Okular::HighlightAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        HighlightAnnotationPrivate()
            : AnnotationPrivate(), m_highlightType( HighlightAnnotation::Highlight )
        {
        }

        void transform( const QTransform &matrix ) override;
        void baseTransform( const QTransform &matrix ) override;
        double distanceSqr( double x, double y, double xScale, double yScale ) const override;
        void setAnnotationProperties( const QDomNode& node ) override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        HighlightAnnotation::HighlightType m_highlightType;
        QList< HighlightAnnotation::Quad > m_highlightQuads;
};

HighlightAnnotation::HighlightAnnotation()
    : Annotation( *new HighlightAnnotationPrivate() )
{
}

HighlightAnnotation::HighlightAnnotation( const QDomNode & description )
    : Annotation( *new HighlightAnnotationPrivate(), description )
{
}

HighlightAnnotation::~HighlightAnnotation()
{
}

void HighlightAnnotation::setHighlightType( HighlightType type )
{
    Q_D( HighlightAnnotation );
    d->m_highlightType = type;
}

HighlightAnnotation::HighlightType HighlightAnnotation::highlightType() const
{
    Q_D( const HighlightAnnotation );
    return d->m_highlightType;
}

QList< HighlightAnnotation::Quad > & HighlightAnnotation::highlightQuads()
{
    Q_D( HighlightAnnotation );
    return d->m_highlightQuads;
}

void HighlightAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    Q_D( const HighlightAnnotation );
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [hl] element
    QDomElement hlElement = document.createElement( QStringLiteral("hl") );
    node.appendChild( hlElement );

    // append the optional attributes
    if ( d->m_highlightType != Highlight )
        hlElement.setAttribute( QStringLiteral("type"), (int)d->m_highlightType );
    if ( d->m_highlightQuads.count() < 1 )
        return;
    // append highlight quads, all children describe quads
    QList< Quad >::const_iterator it = d->m_highlightQuads.begin(), end = d->m_highlightQuads.end();
    for ( ; it != end; ++it )
    {
        QDomElement quadElement = document.createElement( QStringLiteral("quad") );
        hlElement.appendChild( quadElement );
        const Quad & q = *it;
        quadElement.setAttribute( QStringLiteral("ax"), QString::number( q.point( 0 ).x ) );
        quadElement.setAttribute( QStringLiteral("ay"), QString::number( q.point( 0 ).y ) );
        quadElement.setAttribute( QStringLiteral("bx"), QString::number( q.point( 1 ).x ) );
        quadElement.setAttribute( QStringLiteral("by"), QString::number( q.point( 1 ).y ) );
        quadElement.setAttribute( QStringLiteral("cx"), QString::number( q.point( 2 ).x ) );
        quadElement.setAttribute( QStringLiteral("cy"), QString::number( q.point( 2 ).y ) );
        quadElement.setAttribute( QStringLiteral("dx"), QString::number( q.point( 3 ).x ) );
        quadElement.setAttribute( QStringLiteral("dy"), QString::number( q.point( 3 ).y ) );
        if ( q.capStart() )
            quadElement.setAttribute( QStringLiteral("start"), 1 );
        if ( q.capEnd() )
            quadElement.setAttribute( QStringLiteral("end"), 1 );
        quadElement.setAttribute( QStringLiteral("feather"), QString::number( q.feather() ) );
    }
}

Annotation::SubType HighlightAnnotation::subType() const
{
    return AHighlight;
}

void HighlightAnnotationPrivate::transform( const QTransform &matrix )
{
    AnnotationPrivate::transform( matrix );

    QMutableListIterator<HighlightAnnotation::Quad> it( m_highlightQuads );
    while ( it.hasNext() )
        it.next().transform( matrix );
}

void HighlightAnnotationPrivate::baseTransform( const QTransform &matrix )
{
    AnnotationPrivate::baseTransform( matrix );

    QMutableListIterator<HighlightAnnotation::Quad> it( m_highlightQuads );
    while ( it.hasNext() )
        it.next().transform( matrix );
}

void HighlightAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(node);
    m_highlightQuads.clear();

    // loop through the whole children looking for a 'hl' element
    QDomNode subNode = node.firstChild();
    while( subNode.isElement() )
    {
        QDomElement e = subNode.toElement();
        subNode = subNode.nextSibling();
        if ( e.tagName() != QLatin1String("hl") )
            continue;

        // parse the attributes
        if ( e.hasAttribute( QStringLiteral("type") ) )
            m_highlightType = (HighlightAnnotation::HighlightType)e.attribute( QStringLiteral("type") ).toInt();

        // parse all 'quad' subnodes
        QDomNode quadNode = e.firstChild();
        for ( ; quadNode.isElement(); quadNode = quadNode.nextSibling() )
        {
            QDomElement qe = quadNode.toElement();
            if ( qe.tagName() != QLatin1String("quad") )
                continue;

            HighlightAnnotation::Quad q;
            q.setPoint( NormalizedPoint( qe.attribute( QStringLiteral("ax"), QStringLiteral("0.0") ).toDouble(), qe.attribute( QStringLiteral("ay"), QStringLiteral("0.0") ).toDouble() ), 0 );
            q.setPoint( NormalizedPoint( qe.attribute( QStringLiteral("bx"), QStringLiteral("0.0") ).toDouble(), qe.attribute( QStringLiteral("by"), QStringLiteral("0.0") ).toDouble() ), 1 );
            q.setPoint( NormalizedPoint( qe.attribute( QStringLiteral("cx"), QStringLiteral("0.0") ).toDouble(), qe.attribute( QStringLiteral("cy"), QStringLiteral("0.0") ).toDouble() ), 2 );
            q.setPoint( NormalizedPoint( qe.attribute( QStringLiteral("dx"), QStringLiteral("0.0") ).toDouble(), qe.attribute( QStringLiteral("dy"), QStringLiteral("0.0") ).toDouble() ), 3 );
            q.setCapStart( qe.hasAttribute( QStringLiteral("start") ) );
            q.setCapEnd( qe.hasAttribute( QStringLiteral("end") ) );
            q.setFeather( qe.attribute( QStringLiteral("feather"), QStringLiteral("0.1") ).toDouble() );

            q.transform( QTransform() );

            m_highlightQuads.append( q );
        }

        // loading complete
        break;
    }
}

AnnotationPrivate* HighlightAnnotationPrivate::getNewAnnotationPrivate()
{
    return new HighlightAnnotationPrivate();
}

double HighlightAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) const
{
    NormalizedPoint point( x, y );
    double outsideDistance = DBL_MAX;
    for ( const HighlightAnnotation::Quad &quad : m_highlightQuads )
    {
        QLinkedList<NormalizedPoint> pathPoints;

        //first, we check if the point is within the area described by the 4 quads
        //this is the case, if the point is always on one side of each segments delimiting the polygon:
        pathPoints << quad.transformedPoint( 0 );
        int directionVote = 0;
        for ( int i = 1; i < 5; ++i )
        {
            NormalizedPoint thisPoint = quad.transformedPoint( i % 4 );
            directionVote += (isLeftOfVector( pathPoints.back(), thisPoint, point )) ? 1 : -1;
            pathPoints << thisPoint;
        }
        if ( abs( directionVote ) == 4 )
            return 0;

        //if that's not the case, we treat the outline as path and simply determine
        //the distance from the path to the point
        const double thisOutsideDistance = ::distanceSqr( x, y, xScale, yScale, pathPoints );
        if ( thisOutsideDistance < outsideDistance )
            outsideDistance = thisOutsideDistance;
    }

    return outsideDistance;
}

/** StampAnnotation [Annotation] */

class Okular::StampAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        StampAnnotationPrivate()
            : AnnotationPrivate(), m_stampIconName( QStringLiteral("Draft") )
        {
        }
        void setAnnotationProperties( const QDomNode& node ) override;
        bool canBeResized() const override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        QString m_stampIconName;
};

StampAnnotation::StampAnnotation()
    : Annotation( *new StampAnnotationPrivate() )
{
}

StampAnnotation::StampAnnotation( const QDomNode & description )
    : Annotation( *new StampAnnotationPrivate(), description )
{
}

StampAnnotation::~StampAnnotation()
{
}

void StampAnnotation::setStampIconName( const QString &name )
{
    Q_D( StampAnnotation );
    d->m_stampIconName = name;
}

QString StampAnnotation::stampIconName() const
{
    Q_D( const StampAnnotation );
    return d->m_stampIconName;
}

Annotation::SubType StampAnnotation::subType() const
{
    return AStamp;
}

void StampAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    Q_D( const StampAnnotation );
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [stamp] element
    QDomElement stampElement = document.createElement( QStringLiteral("stamp") );
    node.appendChild( stampElement );

    // append the optional attributes
    if ( d->m_stampIconName != QLatin1String("Draft") )
        stampElement.setAttribute( QStringLiteral("icon"), d->m_stampIconName );
}

void StampAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(node);

    // loop through the whole children looking for a 'stamp' element
    QDomNode subNode = node.firstChild();
    while( subNode.isElement() )
    {
        QDomElement e = subNode.toElement();
        subNode = subNode.nextSibling();
        if ( e.tagName() != QLatin1String("stamp") )
            continue;

        // parse the attributes
        if ( e.hasAttribute( QStringLiteral("icon") ) )
            m_stampIconName = e.attribute( QStringLiteral("icon") );

        // loading complete
        break;
    }
}

bool StampAnnotationPrivate::canBeResized() const
{
    return true;
}

AnnotationPrivate* StampAnnotationPrivate::getNewAnnotationPrivate()
{
    return new StampAnnotationPrivate();
}

/** InkAnnotation [Annotation] */

class Okular::InkAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        InkAnnotationPrivate()
            : AnnotationPrivate()
        {
        }

        void transform( const QTransform &matrix ) override;
        void baseTransform( const QTransform &matrix ) override;
        void resetTransformation() override;
        double distanceSqr( double x, double y, double xScale, double yScale ) const override;
        void translate( const NormalizedPoint &coord ) override;
        void setAnnotationProperties( const QDomNode& node ) override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        QList< QLinkedList<NormalizedPoint> > m_inkPaths;
        QList< QLinkedList<NormalizedPoint> > m_transformedInkPaths;
};

InkAnnotation::InkAnnotation()
    : Annotation( *new InkAnnotationPrivate() )
{
}

InkAnnotation::InkAnnotation( const QDomNode & description )
    : Annotation( *new InkAnnotationPrivate(), description )
{
}

InkAnnotation::~InkAnnotation()
{
}

void InkAnnotation::setInkPaths( const QList< QLinkedList<NormalizedPoint> > &paths )
{
    Q_D( InkAnnotation );
    d->m_inkPaths = paths;
}

QList< QLinkedList<NormalizedPoint> > InkAnnotation::inkPaths() const
{
    Q_D( const InkAnnotation );
    return d->m_inkPaths;
}

QList< QLinkedList<NormalizedPoint> > InkAnnotation::transformedInkPaths() const
{
    Q_D( const InkAnnotation );
    return d->m_transformedInkPaths;
}

Annotation::SubType InkAnnotation::subType() const
{
    return AInk;
}

void InkAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    Q_D( const InkAnnotation );
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [ink] element
    QDomElement inkElement = document.createElement( QStringLiteral("ink") );
    node.appendChild( inkElement );

    // append the optional attributes
    if ( d->m_inkPaths.count() < 1 )
        return;

    QList< QLinkedList<NormalizedPoint> >::const_iterator pIt = d->m_inkPaths.begin(), pEnd = d->m_inkPaths.end();
    for ( ; pIt != pEnd; ++pIt )
    {
        QDomElement pathElement = document.createElement( QStringLiteral("path") );
        inkElement.appendChild( pathElement );
        const QLinkedList<NormalizedPoint> & path = *pIt;
        QLinkedList<NormalizedPoint>::const_iterator iIt = path.begin(), iEnd = path.end();
        for ( ; iIt != iEnd; ++iIt )
        {
            const NormalizedPoint & point = *iIt;
            QDomElement pointElement = document.createElement( QStringLiteral("point") );
            pathElement.appendChild( pointElement );
            pointElement.setAttribute( QStringLiteral("x"), QString::number( point.x ) );
            pointElement.setAttribute( QStringLiteral("y"), QString::number( point.y ) );
        }
    }
}

double InkAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) const
{
    double distance = DBL_MAX;
    for ( const QLinkedList<NormalizedPoint> &path : m_transformedInkPaths )
    {
        const double thisDistance = ::distanceSqr( x, y, xScale, yScale, path );
        if ( thisDistance < distance )
            distance = thisDistance;
    }
    return strokeDistance( distance, m_style.width() * xScale / ( m_page->m_width * 2 ) );
}

void InkAnnotationPrivate::transform( const QTransform &matrix )
{
    AnnotationPrivate::transform( matrix );

    for ( int i = 0; i < m_transformedInkPaths.count(); ++i )
    {
        QMutableLinkedListIterator<NormalizedPoint> it( m_transformedInkPaths[ i ] );
        while ( it.hasNext() )
            it.next().transform( matrix );
    }
}

void InkAnnotationPrivate::baseTransform( const QTransform &matrix )
{
    AnnotationPrivate::baseTransform( matrix );

    for ( int i = 0; i < m_inkPaths.count(); ++i )
    {
        QMutableLinkedListIterator<NormalizedPoint> it( m_inkPaths[ i ] );
        while ( it.hasNext() )
            it.next().transform( matrix );
    }
}

void InkAnnotationPrivate::resetTransformation()
{
    AnnotationPrivate::resetTransformation();

    m_transformedInkPaths = m_inkPaths;
}

void InkAnnotationPrivate::translate( const NormalizedPoint &coord )
{
    AnnotationPrivate::translate( coord );

    for ( int i = 0; i < m_inkPaths.count(); ++i )
    {
        QMutableLinkedListIterator<NormalizedPoint> it( m_inkPaths[ i ] );
        while ( it.hasNext() )
        {
            NormalizedPoint& p = it.next();
            p.x = p.x + coord.x;
            p.y = p.y + coord.y;
        }
    }
}

void InkAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(node);
    m_inkPaths.clear();

    // loop through the whole children looking for a 'ink' element
    QDomNode subNode = node.firstChild();
    while( subNode.isElement() )
    {
        QDomElement e = subNode.toElement();
        subNode = subNode.nextSibling();
        if ( e.tagName() != QLatin1String("ink") )
            continue;

        // parse the 'path' subnodes
        QDomNode pathNode = e.firstChild();
        while ( pathNode.isElement() )
        {
            QDomElement pathElement = pathNode.toElement();
            pathNode = pathNode.nextSibling();

            if ( pathElement.tagName() != QLatin1String("path") )
                continue;

            // build each path parsing 'point' subnodes
            QLinkedList<NormalizedPoint> path;
            QDomNode pointNode = pathElement.firstChild();
            while ( pointNode.isElement() )
            {
                QDomElement pointElement = pointNode.toElement();
                pointNode = pointNode.nextSibling();

                if ( pointElement.tagName() != QLatin1String("point") )
                    continue;

                NormalizedPoint p;
                p.x = pointElement.attribute( QStringLiteral("x"), QStringLiteral("0.0") ).toDouble();
                p.y = pointElement.attribute( QStringLiteral("y"), QStringLiteral("0.0") ).toDouble();
                path.append( p );
            }

            // add the path to the path list if it contains at least 2 nodes
            if ( path.count() >= 2 )
                m_inkPaths.append( path );
        }

        // loading complete
        break;
    }

    m_transformedInkPaths = m_inkPaths;
}

AnnotationPrivate* InkAnnotationPrivate::getNewAnnotationPrivate()
{
    return new InkAnnotationPrivate();
}

/** CaretAnnotation [Annotation] */

class Okular::CaretAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        CaretAnnotationPrivate()
            : AnnotationPrivate(), m_symbol( CaretAnnotation::None )
        {
        }

        void setAnnotationProperties( const QDomNode& node ) override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        CaretAnnotation::CaretSymbol m_symbol;
};

static QString caretSymbolToString( CaretAnnotation::CaretSymbol symbol )
{
    switch ( symbol )
    {
        case CaretAnnotation::None:
            return QStringLiteral( "None" );
        case CaretAnnotation::P:
            return QStringLiteral( "P" );
    }
    return QString();
}

static CaretAnnotation::CaretSymbol caretSymbolFromString( const QString &symbol )
{
    if ( symbol == QLatin1String( "None" ) )
        return CaretAnnotation::None;
    else if ( symbol == QLatin1String( "P" ) )
        return CaretAnnotation::P;
    return CaretAnnotation::None;
}

void CaretAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(node);

    // loop through the whole children looking for a 'caret' element
    QDomNode subNode = node.firstChild();
    while( subNode.isElement() )
    {
        QDomElement e = subNode.toElement();
        subNode = subNode.nextSibling();
        if ( e.tagName() != QLatin1String("caret") )
            continue;

        // parse the attributes
        if ( e.hasAttribute( QStringLiteral("symbol") ) )
            m_symbol = caretSymbolFromString( e.attribute( QStringLiteral("symbol") ) );

        // loading complete
        break;
    }
}

AnnotationPrivate* CaretAnnotationPrivate::getNewAnnotationPrivate()
{
    return new CaretAnnotationPrivate();
}

CaretAnnotation::CaretAnnotation()
    : Annotation( *new CaretAnnotationPrivate() )
{
}

CaretAnnotation::CaretAnnotation( const QDomNode & description )
    : Annotation( *new CaretAnnotationPrivate(), description )
{
}

CaretAnnotation::~CaretAnnotation()
{
}

void CaretAnnotation::setCaretSymbol( CaretAnnotation::CaretSymbol symbol )
{
    Q_D( CaretAnnotation );
    d->m_symbol = symbol;
}

CaretAnnotation::CaretSymbol CaretAnnotation::caretSymbol() const
{
    Q_D( const CaretAnnotation );
    return d->m_symbol;
}

Annotation::SubType CaretAnnotation::subType() const
{
    return ACaret;
}

void CaretAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    Q_D( const CaretAnnotation );
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [caret] element
    QDomElement caretElement = document.createElement( QStringLiteral("caret") );
    node.appendChild( caretElement );

    // append the optional attributes
    if ( d->m_symbol != None )
        caretElement.setAttribute( QStringLiteral("symbol"), caretSymbolToString( d->m_symbol ) );
}

/** FileAttachmentAnnotation [Annotation] */

class Okular::FileAttachmentAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        FileAttachmentAnnotationPrivate()
            : AnnotationPrivate(), icon( QStringLiteral("PushPin") ), embfile( nullptr )
        {
        }
        ~FileAttachmentAnnotationPrivate() override
        {
            delete embfile;
        }

        void setAnnotationProperties( const QDomNode& node ) override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        // data fields
        QString icon;
        EmbeddedFile *embfile;
};

void FileAttachmentAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(node);

    // loop through the whole children looking for a 'fileattachment' element
    QDomNode subNode = node.firstChild();
    while( subNode.isElement() )
    {
        QDomElement e = subNode.toElement();
        subNode = subNode.nextSibling();
        if ( e.tagName() != QLatin1String("fileattachment") )
            continue;

        // loading complete
        break;
    }
}

AnnotationPrivate* FileAttachmentAnnotationPrivate::getNewAnnotationPrivate()
{
    return new FileAttachmentAnnotationPrivate();
}

FileAttachmentAnnotation::FileAttachmentAnnotation()
    : Annotation( *new FileAttachmentAnnotationPrivate() )
{
}

FileAttachmentAnnotation::FileAttachmentAnnotation( const QDomNode & description )
    : Annotation( *new FileAttachmentAnnotationPrivate(), description )
{
}

FileAttachmentAnnotation::~FileAttachmentAnnotation()
{
}

void FileAttachmentAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [fileattachment] element
    QDomElement fileAttachmentElement = document.createElement( QStringLiteral("fileattachment") );
    node.appendChild( fileAttachmentElement );
}

Annotation::SubType FileAttachmentAnnotation::subType() const
{
    return AFileAttachment;
}

QString FileAttachmentAnnotation::fileIconName() const
{
    Q_D( const FileAttachmentAnnotation );
    return d->icon;
}

void FileAttachmentAnnotation::setFileIconName( const QString &iconName )
{
    Q_D( FileAttachmentAnnotation );
    d->icon = iconName;
}

EmbeddedFile* FileAttachmentAnnotation::embeddedFile() const
{
    Q_D( const FileAttachmentAnnotation );
    return d->embfile;
}

void FileAttachmentAnnotation::setEmbeddedFile( EmbeddedFile *ef )
{
    Q_D( FileAttachmentAnnotation );
    d->embfile = ef;
}


/** SoundAnnotation [Annotation] */

class Okular::SoundAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        SoundAnnotationPrivate()
            : AnnotationPrivate(), icon( QStringLiteral("Speaker") ), sound( nullptr )
        {
        }
        ~SoundAnnotationPrivate() override
        {
            delete sound;
        }

        void setAnnotationProperties( const QDomNode& node ) override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        // data fields
        QString icon;
        Sound *sound;
};

void SoundAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(node);

    // loop through the whole children looking for a 'sound' element
    QDomNode subNode = node.firstChild();
    while( subNode.isElement() )
    {
        QDomElement e = subNode.toElement();
        subNode = subNode.nextSibling();
        if ( e.tagName() != QLatin1String("sound") )
            continue;

        // loading complete
        break;
    }
}

AnnotationPrivate* SoundAnnotationPrivate::getNewAnnotationPrivate()
{
    return new SoundAnnotationPrivate();
}

SoundAnnotation::SoundAnnotation()
    : Annotation( *new SoundAnnotationPrivate() )
{
}

SoundAnnotation::SoundAnnotation( const QDomNode & description )
    : Annotation( *new SoundAnnotationPrivate(), description )
{
}

SoundAnnotation::~SoundAnnotation()
{
}

void SoundAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [sound] element
    QDomElement soundElement = document.createElement( QStringLiteral("sound") );
    node.appendChild( soundElement );
}

Annotation::SubType SoundAnnotation::subType() const
{
    return ASound;
}

QString SoundAnnotation::soundIconName() const
{
    Q_D( const SoundAnnotation );
    return d->icon;
}

void SoundAnnotation::setSoundIconName( const QString &iconName )
{
    Q_D( SoundAnnotation );
    d->icon = iconName;
}

Sound* SoundAnnotation::sound() const
{
    Q_D( const SoundAnnotation );
    return d->sound;
}

void SoundAnnotation::setSound( Sound *s )
{
    Q_D( SoundAnnotation );
    d->sound = s;
}

/** MovieAnnotation [Annotation] */

class Okular::MovieAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        MovieAnnotationPrivate()
            : AnnotationPrivate(), movie( nullptr )
        {
        }
        ~MovieAnnotationPrivate() override
        {
            delete movie;
        }

        void setAnnotationProperties( const QDomNode& node ) override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        // data fields
        Movie *movie;
};

void MovieAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(node);

    // loop through the whole children looking for a 'movie' element
    QDomNode subNode = node.firstChild();
    while( subNode.isElement() )
    {
        QDomElement e = subNode.toElement();
        subNode = subNode.nextSibling();
        if ( e.tagName() != QLatin1String("movie") )
            continue;

        // loading complete
        break;
    }
}

AnnotationPrivate* MovieAnnotationPrivate::getNewAnnotationPrivate()
{
    return new MovieAnnotationPrivate();
}

MovieAnnotation::MovieAnnotation()
    : Annotation( *new MovieAnnotationPrivate() )
{
}

MovieAnnotation::MovieAnnotation( const QDomNode & description )
    : Annotation( *new MovieAnnotationPrivate(), description )
{
}

MovieAnnotation::~MovieAnnotation()
{
}

void MovieAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [movie] element
    QDomElement movieElement = document.createElement( QStringLiteral("movie") );
    node.appendChild( movieElement );
}

Annotation::SubType MovieAnnotation::subType() const
{
    return AMovie;
}

Movie* MovieAnnotation::movie() const
{
    Q_D( const MovieAnnotation );
    return d->movie;
}

void MovieAnnotation::setMovie( Movie *movie )
{
    Q_D( MovieAnnotation );
    d->movie = movie;
}

/** ScreenAnnotation [Annotation] */

class Okular::ScreenAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        ScreenAnnotationPrivate();
        ~ScreenAnnotationPrivate() override;

        void setAnnotationProperties( const QDomNode& node ) override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        Okular::Action* m_action;
        QMap< Okular::Annotation::AdditionalActionType, Okular::Action* > m_additionalActions;
};

ScreenAnnotationPrivate::ScreenAnnotationPrivate()
    : m_action( nullptr )
{
}

ScreenAnnotationPrivate::~ScreenAnnotationPrivate()
{
    delete m_action;
    qDeleteAll( m_additionalActions );
}

void ScreenAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(node);

    // loop through the whole children looking for a 'screen' element
    QDomNode subNode = node.firstChild();
    while( subNode.isElement() )
    {
        QDomElement e = subNode.toElement();
        subNode = subNode.nextSibling();
        if ( e.tagName() != QLatin1String("screen") )
            continue;

        // loading complete
        break;
    }
}

AnnotationPrivate* ScreenAnnotationPrivate::getNewAnnotationPrivate()
{
    return new ScreenAnnotationPrivate();
}

ScreenAnnotation::ScreenAnnotation()
    : Annotation( *new ScreenAnnotationPrivate() )
{
}

ScreenAnnotation::ScreenAnnotation( const QDomNode & description )
    : Annotation( *new ScreenAnnotationPrivate(), description )
{
}

ScreenAnnotation::~ScreenAnnotation()
{
}

void ScreenAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [screen] element
    QDomElement movieElement = document.createElement( QStringLiteral("screen") );
    node.appendChild( movieElement );
}

Annotation::SubType ScreenAnnotation::subType() const
{
    return AScreen;
}

void ScreenAnnotation::setAdditionalAction( AdditionalActionType type, Action *action )
{
    Q_D( ScreenAnnotation );
    if ( d->m_additionalActions.contains( type ) )
        delete d->m_additionalActions.value( type );

    d->m_additionalActions.insert( type, action );
}

Action* ScreenAnnotation::additionalAction( AdditionalActionType type ) const
{
    Q_D( const ScreenAnnotation );
    if ( !d->m_additionalActions.contains( type ) )
        return nullptr;
    else
        return d->m_additionalActions.value( type );
}

void ScreenAnnotation::setAction( Action *action )
{
    Q_D( ScreenAnnotation );

    delete d->m_action;
    d->m_action = action;
}

Action* ScreenAnnotation::action() const
{
    Q_D( const ScreenAnnotation );
    return d->m_action;
}

/** WidgetAnnotation [Annotation] */

class Okular::WidgetAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        ~WidgetAnnotationPrivate() override;
        void setAnnotationProperties( const QDomNode& node ) override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        QMap< Okular::Annotation::AdditionalActionType, Okular::Action* > m_additionalActions;
};

WidgetAnnotationPrivate::~WidgetAnnotationPrivate()
{
    qDeleteAll( m_additionalActions );
}

void WidgetAnnotationPrivate::setAnnotationProperties( const QDomNode& node )
{
    Okular::AnnotationPrivate::setAnnotationProperties(node);

    // loop through the whole children looking for a 'widget' element
    QDomNode subNode = node.firstChild();
    while( subNode.isElement() )
    {
        QDomElement e = subNode.toElement();
        subNode = subNode.nextSibling();
        if ( e.tagName() != QLatin1String("widget") )
            continue;

        // loading complete
        break;
    }
}

AnnotationPrivate* WidgetAnnotationPrivate::getNewAnnotationPrivate()
{
    return new WidgetAnnotationPrivate();
}

WidgetAnnotation::WidgetAnnotation()
    : Annotation( *new WidgetAnnotationPrivate() )
{
}

WidgetAnnotation::WidgetAnnotation( const QDomNode & description )
    : Annotation( *new WidgetAnnotationPrivate, description )
{
}

WidgetAnnotation::~WidgetAnnotation()
{
}

void WidgetAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
    // recurse to parent objects storing properties
    Annotation::store( node, document );

    // create [widget] element
    QDomElement movieElement = document.createElement( QStringLiteral("widget") );
    node.appendChild( movieElement );
}

Annotation::SubType WidgetAnnotation::subType() const
{
    return AWidget;
}

void WidgetAnnotation::setAdditionalAction( AdditionalActionType type, Action *action )
{
    Q_D( WidgetAnnotation );
    if ( d->m_additionalActions.contains( type ) )
        delete d->m_additionalActions.value( type );

    d->m_additionalActions.insert( type, action );
}

Action* WidgetAnnotation::additionalAction( AdditionalActionType type ) const
{
    Q_D( const WidgetAnnotation );
    if ( !d->m_additionalActions.contains( type ) )
        return nullptr;
    else
        return d->m_additionalActions.value( type );
}

/** RichMediaAnnotation [Annotation] */

class Okular::RichMediaAnnotationPrivate : public Okular::AnnotationPrivate
{
    public:
        RichMediaAnnotationPrivate();
        ~RichMediaAnnotationPrivate() override;
        void setAnnotationProperties( const QDomNode& node ) override;
        AnnotationPrivate* getNewAnnotationPrivate() override;

        // data fields
        Movie *movie;
        EmbeddedFile *embeddedFile;
};

RichMediaAnnotationPrivate::RichMediaAnnotationPrivate()
    : movie( nullptr ), embeddedFile( nullptr )
{
}

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() != QLatin1String("richMedia") )
            continue;

        // loading complete
        break;
    }
}

AnnotationPrivate* RichMediaAnnotationPrivate::getNewAnnotationPrivate()
{
    return new RichMediaAnnotationPrivate();
}

RichMediaAnnotation::RichMediaAnnotation()
    : Annotation( *new RichMediaAnnotationPrivate() )
{
}

RichMediaAnnotation::RichMediaAnnotation( const QDomNode & description )
    : Annotation( *new RichMediaAnnotationPrivate, description )
{
}

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( QStringLiteral("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;
}
