File indexing completed on 2024-04-28 04:32:36

0001 /*
0002     SPDX-FileCopyrightText: 2005 Enrico Ros <eros.kde@email.it>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "annotations.h"
0008 #include "annotations_p.h"
0009 
0010 // qt/kde includes
0011 #include <QApplication>
0012 #include <QColor>
0013 #include <QFile>
0014 #include <QIcon>
0015 #include <QPainter>
0016 #include <QStandardPaths>
0017 #include <QSvgRenderer>
0018 
0019 // DBL_MAX
0020 #include <float.h>
0021 
0022 // local includes
0023 #include "action.h"
0024 #include "document.h"
0025 #include "document_p.h"
0026 #include "movie.h"
0027 #include "page_p.h"
0028 #include "sound.h"
0029 
0030 using namespace Okular;
0031 
0032 /**
0033  * True, if point @p c lies to the left of the vector from @p a to @p b
0034  * @internal
0035  */
0036 static bool isLeftOfVector(const NormalizedPoint &a, const NormalizedPoint &b, const NormalizedPoint &c)
0037 {
0038     // cross product
0039     return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) > 0;
0040 }
0041 
0042 /**
0043  * @brief Calculates distance of the given point @p x @p y @p xScale @p yScale to the @p path
0044  *
0045  * Does piecewise comparison and selects the distance to the closest segment
0046  */
0047 static double distanceSqr(double x, double y, double xScale, double yScale, const QList<NormalizedPoint> &path)
0048 {
0049     double distance = DBL_MAX;
0050     double thisDistance;
0051     QList<NormalizedPoint>::const_iterator i = path.constBegin();
0052     NormalizedPoint lastPoint = *i;
0053 
0054     for (++i; i != path.constEnd(); ++i) {
0055         thisDistance = NormalizedPoint::distanceSqr(x, y, xScale, yScale, lastPoint, (*i));
0056 
0057         if (thisDistance < distance) {
0058             distance = thisDistance;
0059         }
0060 
0061         lastPoint = *i;
0062     }
0063     return distance;
0064 }
0065 
0066 /**
0067  * Given the squared @p distance from the idealized 0-width line and a pen width @p penWidth,
0068  * (not squared!), returns the final distance
0069  *
0070  * @warning The returned distance is not exact:
0071  * We calculate an (exact) squared distance to the ideal (centered) line, and then subtract
0072  * the squared width of the pen:
0073  * a^2 - b^2 where a = "distance from idealized 0-width line" b = "pen width"
0074  * For an exact result, we would want to calculate "(a - b)^2" but that would require
0075  * a square root operation because we only know the squared distance a^2.
0076  *
0077  * However, the approximation is feasible, because:
0078  * error = (a-b)^2 - (a^2 - b^2) = -2ab + 2b^2 = 2b(b - a)
0079  * Therefore:
0080  * lim_{a->b} a^2 - b^2 - a^2 + 2ab - b^2 --> 0
0081  *
0082  * In other words, this approximation will estimate the distance to be slightly more than it actually is
0083  * for as long as we are far "outside" the line, becoming more accurate the closer we get to the line
0084  * boundary. Trivially, it also fulfils (a1 < a2) => ((a1^2 - b^2) < (a2^2 - b^2)) making it monotonic.
0085  * "Inside" of the drawn line, the distance is 0 anyway.
0086  */
0087 static double strokeDistance(double distance, double penWidth)
0088 {
0089     return fmax(distance - pow(penWidth, 2), 0);
0090 }
0091 
0092 // BEGIN AnnotationUtils implementation
0093 Annotation *AnnotationUtils::createAnnotation(const QDomElement &annElement)
0094 {
0095     // safety check on annotation element
0096     if (!annElement.hasAttribute(QStringLiteral("type"))) {
0097         return nullptr;
0098     }
0099 
0100     // build annotation of given type
0101     Annotation *annotation = nullptr;
0102     int typeNumber = annElement.attribute(QStringLiteral("type")).toInt();
0103     switch (typeNumber) {
0104     case Annotation::AText:
0105         annotation = new TextAnnotation(annElement);
0106         break;
0107     case Annotation::ALine:
0108         annotation = new LineAnnotation(annElement);
0109         break;
0110     case Annotation::AGeom:
0111         annotation = new GeomAnnotation(annElement);
0112         break;
0113     case Annotation::AHighlight:
0114         annotation = new HighlightAnnotation(annElement);
0115         break;
0116     case Annotation::AStamp:
0117         annotation = new StampAnnotation(annElement);
0118         break;
0119     case Annotation::AInk:
0120         annotation = new InkAnnotation(annElement);
0121         break;
0122     case Annotation::ACaret:
0123         annotation = new CaretAnnotation(annElement);
0124         break;
0125     }
0126 
0127     // return created annotation
0128     return annotation;
0129 }
0130 
0131 void AnnotationUtils::storeAnnotation(const Annotation *ann, QDomElement &annElement, QDomDocument &document)
0132 {
0133     // save annotation's type as element's attribute
0134     annElement.setAttribute(QStringLiteral("type"), (uint)ann->subType());
0135 
0136     // append all annotation data as children of this node
0137     ann->store(annElement, document);
0138 }
0139 
0140 QDomElement AnnotationUtils::findChildElement(const QDomNode &parentNode, const QString &name)
0141 {
0142     // loop through the whole children and return a 'name' named element
0143     QDomNode subNode = parentNode.firstChild();
0144     while (subNode.isElement()) {
0145         QDomElement element = subNode.toElement();
0146         if (element.tagName() == name) {
0147             return element;
0148         }
0149         subNode = subNode.nextSibling();
0150     }
0151     // if the name can't be found, return a dummy null element
0152     return QDomElement();
0153 }
0154 
0155 QRect AnnotationUtils::annotationGeometry(const Annotation *annotation, double scaleX, double scaleY)
0156 {
0157     const QRect rect = annotation->transformedBoundingRectangle().geometry((int)scaleX, (int)scaleY);
0158     if (annotation->subType() == Annotation::AText && (((TextAnnotation *)annotation)->textType() == TextAnnotation::Linked)) {
0159         // To be honest i have no clue of why the 24,24 is here, maybe to make sure it's not too small?
0160         // But why only for linked text?
0161         const QRect rect24 = QRect((int)(annotation->transformedBoundingRectangle().left * scaleX), (int)(annotation->transformedBoundingRectangle().top * scaleY), 24, 24);
0162         return rect24.united(rect);
0163     }
0164 
0165     return rect;
0166 }
0167 
0168 QPixmap AnnotationUtils::loadStamp(const QString &nameOrPath, int size, bool keepAspectRatio)
0169 {
0170     const QString name = nameOrPath.toLower();
0171 
0172     static std::unique_ptr<QSvgRenderer> svgStampFile;
0173     if (!svgStampFile.get()) {
0174         const QString stampFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/stamps.svg"));
0175         if (!stampFile.isEmpty()) {
0176             svgStampFile = std::make_unique<QSvgRenderer>(stampFile);
0177             if (!svgStampFile->isValid()) {
0178                 svgStampFile.reset();
0179             }
0180         }
0181     }
0182 
0183     QSvgRenderer *r = svgStampFile.get();
0184     if (r && r->isValid() && r->elementExists(name)) {
0185         const QSize stampSize = r->boundsOnElement(name).size().toSize();
0186         const QSize pixmapSize = stampSize.scaled(size, size, keepAspectRatio ? Qt::KeepAspectRatioByExpanding : Qt::IgnoreAspectRatio);
0187         QPixmap pixmap(pixmapSize);
0188         pixmap.fill(Qt::transparent);
0189         QPainter p(&pixmap);
0190         r->render(&p, name);
0191         p.end();
0192         return pixmap;
0193     }
0194 
0195     // _name is a path (do this before loading as icon name to avoid some rare weirdness )
0196     // Check that it exists up front. While pixmap.load() fails, if it is
0197     // actually an icon from theme, the loader will try all supported
0198     // extensions in current workdir before failing
0199     if (QFile::exists(nameOrPath)) {
0200         QPixmap pixmap;
0201         pixmap.load(nameOrPath);
0202         if (!pixmap.isNull()) {
0203             pixmap = pixmap.scaled(size, size, keepAspectRatio ? Qt::KeepAspectRatioByExpanding : Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0204             return pixmap;
0205         }
0206     }
0207 
0208     // _name is an icon name
0209     return QIcon::fromTheme(name).pixmap(size);
0210 }
0211 // END AnnotationUtils implementation
0212 
0213 AnnotationProxy::AnnotationProxy()
0214 {
0215 }
0216 
0217 AnnotationProxy::~AnnotationProxy()
0218 {
0219 }
0220 
0221 // BEGIN Annotation implementation
0222 
0223 class Annotation::Style::Private
0224 {
0225 public:
0226     Private()
0227         : m_opacity(1.0)
0228         , m_width(1.0)
0229         , m_style(Solid)
0230         , m_xCorners(0.0)
0231         , m_yCorners(0.0)
0232         , m_marks(3)
0233         , m_spaces(0)
0234         , m_effect(NoEffect)
0235         , m_effectIntensity(1.0)
0236     {
0237     }
0238 
0239     QColor m_color;
0240     double m_opacity;
0241     double m_width;
0242     LineStyle m_style;
0243     double m_xCorners;
0244     double m_yCorners;
0245     int m_marks;
0246     int m_spaces;
0247     LineEffect m_effect;
0248     double m_effectIntensity;
0249 };
0250 
0251 Annotation::Style::Style()
0252     : d(new Private)
0253 {
0254 }
0255 
0256 Annotation::Style::~Style()
0257 {
0258     delete d;
0259 }
0260 
0261 Annotation::Style::Style(const Style &other)
0262     : d(new Private)
0263 {
0264     *d = *other.d;
0265 }
0266 
0267 Annotation::Style &Annotation::Style::operator=(const Style &other)
0268 {
0269     if (this != &other) {
0270         *d = *other.d;
0271     }
0272 
0273     return *this;
0274 }
0275 
0276 void Annotation::Style::setColor(const QColor &color)
0277 {
0278     d->m_color = color;
0279 }
0280 
0281 QColor Annotation::Style::color() const
0282 {
0283     return d->m_color;
0284 }
0285 
0286 void Annotation::Style::setOpacity(double opacity)
0287 {
0288     d->m_opacity = opacity;
0289 }
0290 
0291 double Annotation::Style::opacity() const
0292 {
0293     return d->m_opacity;
0294 }
0295 
0296 void Annotation::Style::setWidth(double width)
0297 {
0298     d->m_width = width;
0299 }
0300 
0301 double Annotation::Style::width() const
0302 {
0303     return d->m_width;
0304 }
0305 
0306 void Annotation::Style::setLineStyle(LineStyle style)
0307 {
0308     d->m_style = style;
0309 }
0310 
0311 Annotation::LineStyle Annotation::Style::lineStyle() const
0312 {
0313     return d->m_style;
0314 }
0315 
0316 void Annotation::Style::setXCorners(double xCorners)
0317 {
0318     d->m_xCorners = xCorners;
0319 }
0320 
0321 double Annotation::Style::xCorners() const
0322 {
0323     return d->m_xCorners;
0324 }
0325 
0326 void Annotation::Style::setYCorners(double yCorners)
0327 {
0328     d->m_yCorners = yCorners;
0329 }
0330 
0331 double Annotation::Style::yCorners() const
0332 {
0333     return d->m_yCorners;
0334 }
0335 
0336 void Annotation::Style::setMarks(int marks)
0337 {
0338     d->m_marks = marks;
0339 }
0340 
0341 int Annotation::Style::marks() const
0342 {
0343     return d->m_marks;
0344 }
0345 
0346 void Annotation::Style::setSpaces(int spaces)
0347 {
0348     d->m_spaces = spaces;
0349 }
0350 
0351 int Annotation::Style::spaces() const
0352 {
0353     return d->m_spaces;
0354 }
0355 
0356 void Annotation::Style::setLineEffect(LineEffect effect)
0357 {
0358     d->m_effect = effect;
0359 }
0360 
0361 Annotation::LineEffect Annotation::Style::lineEffect() const
0362 {
0363     return d->m_effect;
0364 }
0365 
0366 void Annotation::Style::setEffectIntensity(double intensity)
0367 {
0368     d->m_effectIntensity = intensity;
0369 }
0370 
0371 double Annotation::Style::effectIntensity() const
0372 {
0373     return d->m_effectIntensity;
0374 }
0375 
0376 class Annotation::Window::Private
0377 {
0378 public:
0379     Private()
0380         : m_flags(-1)
0381         , m_width(0)
0382         , m_height(0)
0383     {
0384     }
0385 
0386     int m_flags;
0387     NormalizedPoint m_topLeft;
0388     int m_width;
0389     int m_height;
0390     QString m_title;
0391     QString m_summary;
0392 };
0393 
0394 Annotation::Window::Window()
0395     : d(new Private)
0396 {
0397 }
0398 
0399 Annotation::Window::~Window()
0400 {
0401     delete d;
0402 }
0403 
0404 Annotation::Window::Window(const Window &other)
0405     : d(new Private)
0406 {
0407     *d = *other.d;
0408 }
0409 
0410 Annotation::Window &Annotation::Window::operator=(const Window &other)
0411 {
0412     if (this != &other) {
0413         *d = *other.d;
0414     }
0415 
0416     return *this;
0417 }
0418 
0419 void Annotation::Window::setFlags(int flags)
0420 {
0421     d->m_flags = flags;
0422 }
0423 
0424 int Annotation::Window::flags() const
0425 {
0426     return d->m_flags;
0427 }
0428 
0429 void Annotation::Window::setTopLeft(const NormalizedPoint &point)
0430 {
0431     d->m_topLeft = point;
0432 }
0433 
0434 NormalizedPoint Annotation::Window::topLeft() const
0435 {
0436     return d->m_topLeft;
0437 }
0438 
0439 void Annotation::Window::setWidth(int width)
0440 {
0441     d->m_width = width;
0442 }
0443 
0444 int Annotation::Window::width() const
0445 {
0446     return d->m_width;
0447 }
0448 
0449 void Annotation::Window::setHeight(int height)
0450 {
0451     d->m_height = height;
0452 }
0453 
0454 int Annotation::Window::height() const
0455 {
0456     return d->m_height;
0457 }
0458 
0459 void Annotation::Window::setTitle(const QString &title)
0460 {
0461     d->m_title = title;
0462 }
0463 
0464 QString Annotation::Window::title() const
0465 {
0466     return d->m_title;
0467 }
0468 
0469 void Annotation::Window::setSummary(const QString &summary)
0470 {
0471     d->m_summary = summary;
0472 }
0473 
0474 QString Annotation::Window::summary() const
0475 {
0476     return d->m_summary;
0477 }
0478 
0479 class Annotation::Revision::Private
0480 {
0481 public:
0482     Private()
0483         : m_annotation(nullptr)
0484         , m_scope(Reply)
0485         , m_type(None)
0486     {
0487     }
0488 
0489     Annotation *m_annotation;
0490     RevisionScope m_scope;
0491     RevisionType m_type;
0492 };
0493 
0494 Annotation::Revision::Revision()
0495     : d(new Private)
0496 {
0497 }
0498 
0499 Annotation::Revision::~Revision()
0500 {
0501     delete d;
0502 }
0503 
0504 Annotation::Revision::Revision(const Revision &other)
0505     : d(new Private)
0506 {
0507     *d = *other.d;
0508 }
0509 
0510 Annotation::Revision &Annotation::Revision::operator=(const Revision &other)
0511 {
0512     if (this != &other) {
0513         *d = *other.d;
0514     }
0515 
0516     return *this;
0517 }
0518 
0519 void Annotation::Revision::setAnnotation(Annotation *annotation)
0520 {
0521     d->m_annotation = annotation;
0522 }
0523 
0524 Annotation *Annotation::Revision::annotation() const
0525 {
0526     return d->m_annotation;
0527 }
0528 
0529 void Annotation::Revision::setScope(RevisionScope scope)
0530 {
0531     d->m_scope = scope;
0532 }
0533 
0534 Annotation::RevisionScope Annotation::Revision::scope() const
0535 {
0536     return d->m_scope;
0537 }
0538 
0539 void Annotation::Revision::setType(RevisionType type)
0540 {
0541     d->m_type = type;
0542 }
0543 
0544 Annotation::RevisionType Annotation::Revision::type() const
0545 {
0546     return d->m_type;
0547 }
0548 
0549 AnnotationPrivate::AnnotationPrivate()
0550     : m_page(nullptr)
0551     , m_flags(0)
0552     , m_disposeFunc(nullptr)
0553 {
0554 }
0555 
0556 AnnotationPrivate::~AnnotationPrivate()
0557 {
0558     // delete all children revisions
0559     if (m_revisions.isEmpty()) {
0560         return;
0561     }
0562 
0563     for (const Annotation::Revision &revision : std::as_const(m_revisions)) {
0564         delete revision.annotation();
0565     }
0566 }
0567 
0568 AnnotationPrivate *AnnotationPrivate::get(Annotation *a)
0569 {
0570     return a ? a->d_ptr : nullptr;
0571 }
0572 
0573 Annotation::Annotation(AnnotationPrivate &dd)
0574     : d_ptr(&dd)
0575 {
0576 }
0577 
0578 Annotation::Annotation(AnnotationPrivate &dd, const QDomNode &description)
0579     : d_ptr(&dd)
0580 {
0581     d_ptr->setAnnotationProperties(description);
0582 }
0583 
0584 Annotation::~Annotation()
0585 {
0586     if (d_ptr->m_disposeFunc) {
0587         d_ptr->m_disposeFunc(this);
0588     }
0589 
0590     delete d_ptr;
0591 }
0592 
0593 void Annotation::setAuthor(const QString &author)
0594 {
0595     Q_D(Annotation);
0596     d->m_author = author;
0597 }
0598 
0599 QString Annotation::author() const
0600 {
0601     Q_D(const Annotation);
0602     return d->m_author;
0603 }
0604 
0605 void Annotation::setContents(const QString &contents)
0606 {
0607     Q_D(Annotation);
0608     d->m_contents = contents;
0609 }
0610 
0611 QString Annotation::contents() const
0612 {
0613     Q_D(const Annotation);
0614     return d->m_contents;
0615 }
0616 
0617 void Annotation::setUniqueName(const QString &name)
0618 {
0619     Q_D(Annotation);
0620     d->m_uniqueName = name;
0621 }
0622 
0623 QString Annotation::uniqueName() const
0624 {
0625     Q_D(const Annotation);
0626     return d->m_uniqueName;
0627 }
0628 
0629 void Annotation::setModificationDate(const QDateTime &date)
0630 {
0631     Q_D(Annotation);
0632     d->m_modifyDate = date;
0633 }
0634 
0635 QDateTime Annotation::modificationDate() const
0636 {
0637     Q_D(const Annotation);
0638     return d->m_modifyDate;
0639 }
0640 
0641 void Annotation::setCreationDate(const QDateTime &date)
0642 {
0643     Q_D(Annotation);
0644     d->m_creationDate = date;
0645 }
0646 
0647 QDateTime Annotation::creationDate() const
0648 {
0649     Q_D(const Annotation);
0650     return d->m_creationDate;
0651 }
0652 
0653 void Annotation::setFlags(int flags)
0654 {
0655     Q_D(Annotation);
0656     d->m_flags = flags;
0657 }
0658 
0659 int Annotation::flags() const
0660 {
0661     Q_D(const Annotation);
0662     return d->m_flags;
0663 }
0664 
0665 void Annotation::setBoundingRectangle(const NormalizedRect &rectangle)
0666 {
0667     Q_D(Annotation);
0668     d->m_boundary = rectangle;
0669     d->resetTransformation();
0670     if (d->m_page) {
0671         d->transform(d->m_page->rotationMatrix());
0672     }
0673 }
0674 
0675 NormalizedRect Annotation::boundingRectangle() const
0676 {
0677     Q_D(const Annotation);
0678     return d->m_boundary;
0679 }
0680 
0681 NormalizedRect Annotation::transformedBoundingRectangle() const
0682 {
0683     Q_D(const Annotation);
0684     return d->m_transformedBoundary;
0685 }
0686 
0687 void Annotation::translate(const NormalizedPoint &coord)
0688 {
0689     Q_D(Annotation);
0690     d->translate(coord);
0691     d->resetTransformation();
0692     if (d->m_page) {
0693         d->transform(d->m_page->rotationMatrix());
0694     }
0695 }
0696 
0697 void Annotation::adjust(const NormalizedPoint &deltaCoord1, const NormalizedPoint &deltaCoord2)
0698 {
0699     Q_D(Annotation);
0700     d->adjust(deltaCoord1, deltaCoord2);
0701     d->resetTransformation();
0702     if (d->m_page) {
0703         d->transform(d->m_page->rotationMatrix());
0704     }
0705 }
0706 
0707 bool Annotation::openDialogAfterCreation() const
0708 {
0709     Q_D(const Annotation);
0710     return d->openDialogAfterCreation();
0711 }
0712 
0713 Annotation::Style &Annotation::style()
0714 {
0715     Q_D(Annotation);
0716     return d->m_style;
0717 }
0718 
0719 const Annotation::Style &Annotation::style() const
0720 {
0721     Q_D(const Annotation);
0722     return d->m_style;
0723 }
0724 
0725 Annotation::Window &Annotation::window()
0726 {
0727     Q_D(Annotation);
0728     return d->m_window;
0729 }
0730 
0731 const Annotation::Window &Annotation::window() const
0732 {
0733     Q_D(const Annotation);
0734     return d->m_window;
0735 }
0736 
0737 QList<Annotation::Revision> &Annotation::revisions()
0738 {
0739     Q_D(Annotation);
0740     return d->m_revisions;
0741 }
0742 
0743 const QList<Annotation::Revision> &Annotation::revisions() const
0744 {
0745     Q_D(const Annotation);
0746     return d->m_revisions;
0747 }
0748 
0749 void Annotation::setNativeId(const QVariant &id)
0750 {
0751     Q_D(Annotation);
0752     d->m_nativeId = id;
0753 }
0754 
0755 QVariant Annotation::nativeId() const
0756 {
0757     Q_D(const Annotation);
0758     return d->m_nativeId;
0759 }
0760 
0761 void Annotation::setDisposeDataFunction(DisposeDataFunction func)
0762 {
0763     Q_D(Annotation);
0764     d->m_disposeFunc = func;
0765 }
0766 
0767 bool Annotation::canBeMoved() const
0768 {
0769     Q_D(const Annotation);
0770 
0771     // Don't move annotations if they cannot be modified
0772     if (!d->m_page || !d->m_page->m_doc->m_parent->canModifyPageAnnotation(this)) {
0773         return false;
0774     }
0775 
0776     // highlight "requires" to be "bounded" to text, and that's tricky for now
0777     if (subType() == AHighlight) {
0778         return false;
0779     }
0780 
0781     return true;
0782 }
0783 
0784 bool Annotation::canBeResized() const
0785 {
0786     Q_D(const Annotation);
0787 
0788     // Don't resize annotations if they cannot be modified
0789     if (!d->m_page || !d->m_page->m_doc->m_parent->canModifyPageAnnotation(this)) {
0790         return false;
0791     }
0792 
0793     return d->canBeResized();
0794 }
0795 
0796 void Annotation::store(QDomNode &annNode, QDomDocument &document) const
0797 {
0798     Q_D(const Annotation);
0799     // create [base] element of the annotation node
0800     QDomElement e = document.createElement(QStringLiteral("base"));
0801     annNode.appendChild(e);
0802 
0803     // store -contents- attributes
0804     if (!d->m_author.isEmpty()) {
0805         e.setAttribute(QStringLiteral("author"), d->m_author);
0806     }
0807     if (!d->m_contents.isEmpty()) {
0808         e.setAttribute(QStringLiteral("contents"), d->m_contents);
0809     }
0810     if (!d->m_uniqueName.isEmpty()) {
0811         e.setAttribute(QStringLiteral("uniqueName"), d->m_uniqueName);
0812     }
0813     if (d->m_modifyDate.isValid()) {
0814         e.setAttribute(QStringLiteral("modifyDate"), d->m_modifyDate.toString(Qt::ISODate));
0815     }
0816     if (d->m_creationDate.isValid()) {
0817         e.setAttribute(QStringLiteral("creationDate"), d->m_creationDate.toString(Qt::ISODate));
0818     }
0819 
0820     // store -other- attributes
0821     if (d->m_flags) { // Strip internal flags
0822         e.setAttribute(QStringLiteral("flags"), d->m_flags & ~(External | ExternallyDrawn | BeingMoved | BeingResized));
0823     }
0824     if (d->m_style.color().isValid()) {
0825         e.setAttribute(QStringLiteral("color"), d->m_style.color().name(QColor::HexArgb));
0826     }
0827     if (d->m_style.opacity() != 1.0) {
0828         e.setAttribute(QStringLiteral("opacity"), QString::number(d->m_style.opacity()));
0829     }
0830 
0831     // Sub-Node-1 - boundary
0832     QDomElement bE = document.createElement(QStringLiteral("boundary"));
0833     e.appendChild(bE);
0834     bE.setAttribute(QStringLiteral("l"), QString::number(d->m_boundary.left));
0835     bE.setAttribute(QStringLiteral("t"), QString::number(d->m_boundary.top));
0836     bE.setAttribute(QStringLiteral("r"), QString::number(d->m_boundary.right));
0837     bE.setAttribute(QStringLiteral("b"), QString::number(d->m_boundary.bottom));
0838 
0839     // Sub-Node-2 - penStyle
0840     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) {
0841         QDomElement psE = document.createElement(QStringLiteral("penStyle"));
0842         e.appendChild(psE);
0843         psE.setAttribute(QStringLiteral("width"), QString::number(d->m_style.width()));
0844         psE.setAttribute(QStringLiteral("style"), (int)d->m_style.lineStyle());
0845         psE.setAttribute(QStringLiteral("xcr"), QString::number(d->m_style.xCorners()));
0846         psE.setAttribute(QStringLiteral("ycr"), QString::number(d->m_style.yCorners()));
0847         psE.setAttribute(QStringLiteral("marks"), d->m_style.marks());
0848         psE.setAttribute(QStringLiteral("spaces"), d->m_style.spaces());
0849     }
0850 
0851     // Sub-Node-3 - penEffect
0852     if (d->m_style.lineEffect() != NoEffect || d->m_style.effectIntensity() != 1.0) {
0853         QDomElement peE = document.createElement(QStringLiteral("penEffect"));
0854         e.appendChild(peE);
0855         peE.setAttribute(QStringLiteral("effect"), (int)d->m_style.lineEffect());
0856         peE.setAttribute(QStringLiteral("intensity"), QString::number(d->m_style.effectIntensity()));
0857     }
0858 
0859     // Sub-Node-4 - window
0860     if (d->m_window.flags() != -1 || !d->m_window.title().isEmpty() || !d->m_window.summary().isEmpty()) {
0861         QDomElement wE = document.createElement(QStringLiteral("window"));
0862         e.appendChild(wE);
0863         wE.setAttribute(QStringLiteral("flags"), d->m_window.flags());
0864         wE.setAttribute(QStringLiteral("top"), QString::number(d->m_window.topLeft().x));
0865         wE.setAttribute(QStringLiteral("left"), QString::number(d->m_window.topLeft().y));
0866         wE.setAttribute(QStringLiteral("width"), d->m_window.width());
0867         wE.setAttribute(QStringLiteral("height"), d->m_window.height());
0868         wE.setAttribute(QStringLiteral("title"), d->m_window.title());
0869         wE.setAttribute(QStringLiteral("summary"), d->m_window.summary());
0870     }
0871 
0872     // create [revision] element of the annotation node (if any)
0873     if (d->m_revisions.isEmpty()) {
0874         return;
0875     }
0876 
0877     // add all revisions as children of revisions element
0878     for (const Revision &revision : std::as_const(d->m_revisions)) {
0879         // create revision element
0880         QDomElement r = document.createElement(QStringLiteral("revision"));
0881         annNode.appendChild(r);
0882         // set element attributes
0883         r.setAttribute(QStringLiteral("revScope"), (int)revision.scope());
0884         r.setAttribute(QStringLiteral("revType"), (int)revision.type());
0885         // use revision as the annotation element, so fill it up
0886         AnnotationUtils::storeAnnotation(revision.annotation(), r, document);
0887     }
0888 }
0889 
0890 QDomNode Annotation::getAnnotationPropertiesDomNode() const
0891 {
0892     QDomDocument doc(QStringLiteral("documentInfo"));
0893     QDomElement node = doc.createElement(QStringLiteral("annotation"));
0894 
0895     store(node, doc);
0896     return node;
0897 }
0898 
0899 void Annotation::setAnnotationProperties(const QDomNode &node)
0900 {
0901     // Save off internal properties that aren't contained in node
0902     Okular::PagePrivate *p = d_ptr->m_page;
0903     QVariant nativeID = d_ptr->m_nativeId;
0904     const int internalFlags = d_ptr->m_flags & (External | ExternallyDrawn | BeingMoved | BeingResized);
0905     Annotation::DisposeDataFunction disposeFunc = d_ptr->m_disposeFunc;
0906 
0907     // Replace AnnotationPrivate object with a fresh copy
0908     AnnotationPrivate *new_d_ptr = d_ptr->getNewAnnotationPrivate();
0909     delete (d_ptr);
0910     d_ptr = new_d_ptr;
0911 
0912     // Set the annotations properties from node
0913     d_ptr->setAnnotationProperties(node);
0914 
0915     // Restore internal properties
0916     d_ptr->m_page = p;
0917     d_ptr->m_nativeId = nativeID;
0918     d_ptr->m_flags = d_ptr->m_flags | internalFlags;
0919     d_ptr->m_disposeFunc = disposeFunc;
0920 
0921     // Transform annotation to current page rotation
0922     d_ptr->transform(d_ptr->m_page->rotationMatrix());
0923 }
0924 
0925 double AnnotationPrivate::distanceSqr(double x, double y, double xScale, double yScale) const
0926 {
0927     return m_transformedBoundary.distanceSqr(x, y, xScale, yScale);
0928 }
0929 
0930 void AnnotationPrivate::annotationTransform(const QTransform &matrix)
0931 {
0932     resetTransformation();
0933     transform(matrix);
0934 }
0935 
0936 void AnnotationPrivate::transform(const QTransform &matrix)
0937 {
0938     m_transformedBoundary.transform(matrix);
0939 }
0940 
0941 void AnnotationPrivate::baseTransform(const QTransform &matrix)
0942 {
0943     m_boundary.transform(matrix);
0944 }
0945 
0946 void AnnotationPrivate::resetTransformation()
0947 {
0948     m_transformedBoundary = m_boundary;
0949 }
0950 
0951 void AnnotationPrivate::translate(const NormalizedPoint &coord)
0952 {
0953     m_boundary.left = m_boundary.left + coord.x;
0954     m_boundary.right = m_boundary.right + coord.x;
0955     m_boundary.top = m_boundary.top + coord.y;
0956     m_boundary.bottom = m_boundary.bottom + coord.y;
0957 }
0958 
0959 void AnnotationPrivate::adjust(const NormalizedPoint &deltaCoord1, const NormalizedPoint &deltaCoord2)
0960 {
0961     m_boundary.left = m_boundary.left + qBound(-m_boundary.left, deltaCoord1.x, m_boundary.right - m_boundary.left);
0962     m_boundary.top = m_boundary.top + qBound(-m_boundary.top, deltaCoord1.y, m_boundary.bottom - m_boundary.top);
0963     ;
0964     m_boundary.right = m_boundary.right + qBound(m_boundary.left - m_boundary.right, deltaCoord2.x, 1. - m_boundary.right);
0965     m_boundary.bottom = m_boundary.bottom + qBound(m_boundary.top - m_boundary.bottom, deltaCoord2.y, 1. - m_boundary.bottom);
0966 }
0967 
0968 bool AnnotationPrivate::openDialogAfterCreation() const
0969 {
0970     return false;
0971 }
0972 
0973 void AnnotationPrivate::setAnnotationProperties(const QDomNode &node)
0974 {
0975     // get the [base] element of the annotation node
0976     QDomElement e = AnnotationUtils::findChildElement(node, QStringLiteral("base"));
0977     if (e.isNull()) {
0978         return;
0979     }
0980 
0981     // parse -contents- attributes
0982     if (e.hasAttribute(QStringLiteral("author"))) {
0983         m_author = e.attribute(QStringLiteral("author"));
0984     }
0985     if (e.hasAttribute(QStringLiteral("contents"))) {
0986         m_contents = e.attribute(QStringLiteral("contents"));
0987     }
0988     if (e.hasAttribute(QStringLiteral("uniqueName"))) {
0989         m_uniqueName = e.attribute(QStringLiteral("uniqueName"));
0990     }
0991     if (e.hasAttribute(QStringLiteral("modifyDate"))) {
0992         m_modifyDate = QDateTime::fromString(e.attribute(QStringLiteral("modifyDate")), Qt::ISODate);
0993     }
0994     if (e.hasAttribute(QStringLiteral("creationDate"))) {
0995         m_creationDate = QDateTime::fromString(e.attribute(QStringLiteral("creationDate")), Qt::ISODate);
0996     }
0997 
0998     // parse -other- attributes
0999     if (e.hasAttribute(QStringLiteral("flags"))) {
1000         m_flags = e.attribute(QStringLiteral("flags")).toInt();
1001     }
1002     if (e.hasAttribute(QStringLiteral("color"))) {
1003         m_style.setColor(QColor(e.attribute(QStringLiteral("color"))));
1004     }
1005     if (e.hasAttribute(QStringLiteral("opacity"))) {
1006         m_style.setOpacity(e.attribute(QStringLiteral("opacity")).toDouble());
1007     }
1008 
1009     // parse -the-subnodes- (describing Style, Window, Revision(s) structures)
1010     // Note: all subnodes if present must be 'attributes complete'
1011     QDomNode eSubNode = e.firstChild();
1012     while (eSubNode.isElement()) {
1013         QDomElement ee = eSubNode.toElement();
1014         eSubNode = eSubNode.nextSibling();
1015 
1016         // parse boundary
1017         if (ee.tagName() == QLatin1String("boundary")) {
1018             m_boundary = NormalizedRect(ee.attribute(QStringLiteral("l")).toDouble(), ee.attribute(QStringLiteral("t")).toDouble(), ee.attribute(QStringLiteral("r")).toDouble(), ee.attribute(QStringLiteral("b")).toDouble());
1019         }
1020         // parse penStyle if not default
1021         else if (ee.tagName() == QLatin1String("penStyle")) {
1022             m_style.setWidth(ee.attribute(QStringLiteral("width")).toDouble());
1023             m_style.setLineStyle((Annotation::LineStyle)ee.attribute(QStringLiteral("style")).toInt());
1024             m_style.setXCorners(ee.attribute(QStringLiteral("xcr")).toDouble());
1025             m_style.setYCorners(ee.attribute(QStringLiteral("ycr")).toDouble());
1026             m_style.setMarks(ee.attribute(QStringLiteral("marks")).toInt());
1027             m_style.setSpaces(ee.attribute(QStringLiteral("spaces")).toInt());
1028         }
1029         // parse effectStyle if not default
1030         else if (ee.tagName() == QLatin1String("penEffect")) {
1031             m_style.setLineEffect((Annotation::LineEffect)ee.attribute(QStringLiteral("effect")).toInt());
1032             m_style.setEffectIntensity(ee.attribute(QStringLiteral("intensity")).toDouble());
1033         }
1034         // parse window if present
1035         else if (ee.tagName() == QLatin1String("window")) {
1036             m_window.setFlags(ee.attribute(QStringLiteral("flags")).toInt());
1037             m_window.setTopLeft(NormalizedPoint(ee.attribute(QStringLiteral("top")).toDouble(), ee.attribute(QStringLiteral("left")).toDouble()));
1038             m_window.setWidth(ee.attribute(QStringLiteral("width")).toInt());
1039             m_window.setHeight(ee.attribute(QStringLiteral("height")).toInt());
1040             m_window.setTitle(ee.attribute(QStringLiteral("title")));
1041             m_window.setSummary(ee.attribute(QStringLiteral("summary")));
1042         }
1043     }
1044 
1045     // get the [revisions] element of the annotation node
1046     QDomNode revNode = node.firstChild();
1047     for (; revNode.isElement(); revNode = revNode.nextSibling()) {
1048         QDomElement revElement = revNode.toElement();
1049         if (revElement.tagName() != QLatin1String("revision")) {
1050             continue;
1051         }
1052 
1053         // compile the Revision structure crating annotation
1054         Annotation::Revision revision;
1055         revision.setScope((Annotation::RevisionScope)revElement.attribute(QStringLiteral("revScope")).toInt());
1056         revision.setType((Annotation::RevisionType)revElement.attribute(QStringLiteral("revType")).toInt());
1057         revision.setAnnotation(AnnotationUtils::createAnnotation(revElement));
1058 
1059         // if annotation is valid, add revision to internal list
1060         if (revision.annotation()) {
1061             m_revisions.append(revision);
1062         }
1063     }
1064 
1065     m_transformedBoundary = m_boundary;
1066 }
1067 
1068 bool AnnotationPrivate::canBeResized() const
1069 {
1070     return false;
1071 }
1072 
1073 // END Annotation implementation
1074 
1075 /** TextAnnotation [Annotation] */
1076 
1077 class Okular::TextAnnotationPrivate : public Okular::AnnotationPrivate
1078 {
1079 public:
1080     TextAnnotationPrivate()
1081         : AnnotationPrivate()
1082         , m_textType(TextAnnotation::Linked)
1083         , m_textIcon(QStringLiteral("Comment"))
1084         , m_inplaceAlign(0)
1085         , m_inplaceIntent(TextAnnotation::Unknown)
1086     {
1087     }
1088 
1089     void transform(const QTransform &matrix) override;
1090     void baseTransform(const QTransform &matrix) override;
1091     void resetTransformation() override;
1092     void translate(const NormalizedPoint &coord) override;
1093     bool openDialogAfterCreation() const override;
1094     void setAnnotationProperties(const QDomNode &node) override;
1095     bool canBeResized() const override;
1096     AnnotationPrivate *getNewAnnotationPrivate() override;
1097 
1098     TextAnnotation::TextType m_textType;
1099     QString m_textIcon;
1100     QFont m_textFont;
1101     QColor m_textColor;
1102     int m_inplaceAlign;
1103     NormalizedPoint m_inplaceCallout[3];
1104     NormalizedPoint m_transformedInplaceCallout[3];
1105     TextAnnotation::InplaceIntent m_inplaceIntent;
1106 };
1107 
1108 /*
1109   The default textIcon for text annotation is Note as the PDF Reference says
1110 */
1111 TextAnnotation::TextAnnotation()
1112     : Annotation(*new TextAnnotationPrivate())
1113 {
1114 }
1115 
1116 TextAnnotation::TextAnnotation(const QDomNode &description)
1117     : Annotation(*new TextAnnotationPrivate(), description)
1118 {
1119 }
1120 
1121 TextAnnotation::~TextAnnotation()
1122 {
1123 }
1124 
1125 void TextAnnotation::setTextType(TextType textType)
1126 {
1127     Q_D(TextAnnotation);
1128     d->m_textType = textType;
1129 }
1130 
1131 TextAnnotation::TextType TextAnnotation::textType() const
1132 {
1133     Q_D(const TextAnnotation);
1134     return d->m_textType;
1135 }
1136 
1137 void TextAnnotation::setTextIcon(const QString &icon)
1138 {
1139     Q_D(TextAnnotation);
1140     d->m_textIcon = icon;
1141 }
1142 
1143 QString TextAnnotation::textIcon() const
1144 {
1145     Q_D(const TextAnnotation);
1146     return d->m_textIcon;
1147 }
1148 
1149 void TextAnnotation::setTextFont(const QFont &font)
1150 {
1151     Q_D(TextAnnotation);
1152     d->m_textFont = font;
1153 }
1154 
1155 QFont TextAnnotation::textFont() const
1156 {
1157     Q_D(const TextAnnotation);
1158     return d->m_textFont;
1159 }
1160 
1161 void TextAnnotation::setTextColor(const QColor &color)
1162 {
1163     Q_D(TextAnnotation);
1164     d->m_textColor = color;
1165 }
1166 
1167 QColor TextAnnotation::textColor() const
1168 {
1169     Q_D(const TextAnnotation);
1170     return d->m_textColor;
1171 }
1172 
1173 void TextAnnotation::setInplaceAlignment(int alignment)
1174 {
1175     Q_D(TextAnnotation);
1176     d->m_inplaceAlign = alignment;
1177 }
1178 
1179 int TextAnnotation::inplaceAlignment() const
1180 {
1181     Q_D(const TextAnnotation);
1182     return d->m_inplaceAlign;
1183 }
1184 
1185 void TextAnnotation::setInplaceCallout(const NormalizedPoint &point, int index)
1186 {
1187     if (index < 0 || index > 2) {
1188         return;
1189     }
1190 
1191     Q_D(TextAnnotation);
1192     d->m_inplaceCallout[index] = point;
1193 }
1194 
1195 NormalizedPoint TextAnnotation::inplaceCallout(int index) const
1196 {
1197     if (index < 0 || index > 2) {
1198         return NormalizedPoint();
1199     }
1200 
1201     Q_D(const TextAnnotation);
1202     return d->m_inplaceCallout[index];
1203 }
1204 
1205 NormalizedPoint TextAnnotation::transformedInplaceCallout(int index) const
1206 {
1207     if (index < 0 || index > 2) {
1208         return NormalizedPoint();
1209     }
1210 
1211     Q_D(const TextAnnotation);
1212     return d->m_transformedInplaceCallout[index];
1213 }
1214 
1215 void TextAnnotation::setInplaceIntent(InplaceIntent intent)
1216 {
1217     Q_D(TextAnnotation);
1218     d->m_inplaceIntent = intent;
1219 }
1220 
1221 TextAnnotation::InplaceIntent TextAnnotation::inplaceIntent() const
1222 {
1223     Q_D(const TextAnnotation);
1224     return d->m_inplaceIntent;
1225 }
1226 
1227 Annotation::SubType TextAnnotation::subType() const
1228 {
1229     return AText;
1230 }
1231 
1232 void TextAnnotation::store(QDomNode &node, QDomDocument &document) const
1233 {
1234     Q_D(const TextAnnotation);
1235     // recurse to parent objects storing properties
1236     Annotation::store(node, document);
1237 
1238     // create [text] element
1239     QDomElement textElement = document.createElement(QStringLiteral("text"));
1240     node.appendChild(textElement);
1241 
1242     // store the optional attributes
1243     if (d->m_textType != Linked) {
1244         textElement.setAttribute(QStringLiteral("type"), (int)d->m_textType);
1245     }
1246     if (!d->m_textIcon.isEmpty()) {
1247         textElement.setAttribute(QStringLiteral("icon"), d->m_textIcon);
1248     }
1249     if (d->m_textFont != QApplication::font()) {
1250         textElement.setAttribute(QStringLiteral("font"), d->m_textFont.toString());
1251     }
1252     if (d->m_textColor.isValid()) {
1253         textElement.setAttribute(QStringLiteral("fontColor"), d->m_textColor.name());
1254     }
1255     if (d->m_inplaceAlign) {
1256         textElement.setAttribute(QStringLiteral("align"), d->m_inplaceAlign);
1257     }
1258     if (d->m_inplaceIntent != Unknown) {
1259         textElement.setAttribute(QStringLiteral("intent"), (int)d->m_inplaceIntent);
1260     }
1261 
1262     // Sub-Node - callout
1263     if (d->m_inplaceCallout[0].x != 0.0) {
1264         QDomElement calloutElement = document.createElement(QStringLiteral("callout"));
1265         textElement.appendChild(calloutElement);
1266         calloutElement.setAttribute(QStringLiteral("ax"), QString::number(d->m_inplaceCallout[0].x));
1267         calloutElement.setAttribute(QStringLiteral("ay"), QString::number(d->m_inplaceCallout[0].y));
1268         calloutElement.setAttribute(QStringLiteral("bx"), QString::number(d->m_inplaceCallout[1].x));
1269         calloutElement.setAttribute(QStringLiteral("by"), QString::number(d->m_inplaceCallout[1].y));
1270         calloutElement.setAttribute(QStringLiteral("cx"), QString::number(d->m_inplaceCallout[2].x));
1271         calloutElement.setAttribute(QStringLiteral("cy"), QString::number(d->m_inplaceCallout[2].y));
1272     }
1273 }
1274 
1275 void TextAnnotationPrivate::transform(const QTransform &matrix)
1276 {
1277     AnnotationPrivate::transform(matrix);
1278 
1279     for (NormalizedPoint &np : m_transformedInplaceCallout) {
1280         np.transform(matrix);
1281     }
1282 }
1283 
1284 void TextAnnotationPrivate::baseTransform(const QTransform &matrix)
1285 {
1286     AnnotationPrivate::baseTransform(matrix);
1287 
1288     for (NormalizedPoint &np : m_inplaceCallout) {
1289         np.transform(matrix);
1290     }
1291 }
1292 
1293 void TextAnnotationPrivate::resetTransformation()
1294 {
1295     AnnotationPrivate::resetTransformation();
1296 
1297     for (int i = 0; i < 3; ++i) {
1298         m_transformedInplaceCallout[i] = m_inplaceCallout[i];
1299     }
1300 }
1301 
1302 void TextAnnotationPrivate::translate(const NormalizedPoint &coord)
1303 {
1304     AnnotationPrivate::translate(coord);
1305 
1306 #define ADD_COORD(c1, c2)                                                                                                                                                                                                                      \
1307     {                                                                                                                                                                                                                                          \
1308         c1.x = c1.x + c2.x;                                                                                                                                                                                                                    \
1309         c1.y = c1.y + c2.y;                                                                                                                                                                                                                    \
1310     }
1311     ADD_COORD(m_inplaceCallout[0], coord)
1312     ADD_COORD(m_inplaceCallout[1], coord)
1313     ADD_COORD(m_inplaceCallout[2], coord)
1314 #undef ADD_COORD
1315 }
1316 
1317 bool TextAnnotationPrivate::openDialogAfterCreation() const
1318 {
1319     return (m_textType == Okular::TextAnnotation::Linked) || (m_inplaceIntent == TextAnnotation::InplaceIntent::Unknown);
1320 }
1321 
1322 void TextAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
1323 {
1324     Okular::AnnotationPrivate::setAnnotationProperties(node);
1325 
1326     // loop through the whole children looking for a 'text' element
1327     QDomNode subNode = node.firstChild();
1328     while (subNode.isElement()) {
1329         QDomElement e = subNode.toElement();
1330         subNode = subNode.nextSibling();
1331         if (e.tagName() != QLatin1String("text")) {
1332             continue;
1333         }
1334 
1335         // parse the attributes
1336         if (e.hasAttribute(QStringLiteral("type"))) {
1337             m_textType = (TextAnnotation::TextType)e.attribute(QStringLiteral("type")).toInt();
1338         }
1339         if (e.hasAttribute(QStringLiteral("icon"))) {
1340             m_textIcon = e.attribute(QStringLiteral("icon"));
1341         }
1342         if (e.hasAttribute(QStringLiteral("font"))) {
1343             m_textFont.fromString(e.attribute(QStringLiteral("font")));
1344         }
1345         if (e.hasAttribute(QStringLiteral("fontColor"))) {
1346             m_textColor = QColor(e.attribute(QStringLiteral("fontColor")));
1347         }
1348         if (e.hasAttribute(QStringLiteral("align"))) {
1349             m_inplaceAlign = e.attribute(QStringLiteral("align")).toInt();
1350         }
1351         if (e.hasAttribute(QStringLiteral("intent"))) {
1352             m_inplaceIntent = (TextAnnotation::InplaceIntent)e.attribute(QStringLiteral("intent")).toInt();
1353         }
1354 
1355         // parse the subnodes
1356         QDomNode eSubNode = e.firstChild();
1357         while (eSubNode.isElement()) {
1358             QDomElement ee = eSubNode.toElement();
1359             eSubNode = eSubNode.nextSibling();
1360 
1361             if (ee.tagName() == QLatin1String("escapedText")) {
1362                 m_contents = ee.firstChild().toCDATASection().data();
1363             } else if (ee.tagName() == QLatin1String("callout")) {
1364                 m_inplaceCallout[0].x = ee.attribute(QStringLiteral("ax")).toDouble();
1365                 m_inplaceCallout[0].y = ee.attribute(QStringLiteral("ay")).toDouble();
1366                 m_inplaceCallout[1].x = ee.attribute(QStringLiteral("bx")).toDouble();
1367                 m_inplaceCallout[1].y = ee.attribute(QStringLiteral("by")).toDouble();
1368                 m_inplaceCallout[2].x = ee.attribute(QStringLiteral("cx")).toDouble();
1369                 m_inplaceCallout[2].y = ee.attribute(QStringLiteral("cy")).toDouble();
1370             }
1371         }
1372 
1373         // loading complete
1374         break;
1375     }
1376 
1377     for (int i = 0; i < 3; ++i) {
1378         m_transformedInplaceCallout[i] = m_inplaceCallout[i];
1379     }
1380 }
1381 
1382 bool TextAnnotationPrivate::canBeResized() const
1383 {
1384     if (m_textType != TextAnnotation::Linked) {
1385         return true;
1386     }
1387     return false;
1388 }
1389 
1390 AnnotationPrivate *TextAnnotationPrivate::getNewAnnotationPrivate()
1391 {
1392     return new TextAnnotationPrivate();
1393 }
1394 
1395 /** LineAnnotation [Annotation] */
1396 
1397 class Okular::LineAnnotationPrivate : public Okular::AnnotationPrivate
1398 {
1399 public:
1400     LineAnnotationPrivate()
1401         : AnnotationPrivate()
1402         , m_lineStartStyle(LineAnnotation::None)
1403         , m_lineEndStyle(LineAnnotation::None)
1404         , m_lineClosed(false)
1405         , m_lineShowCaption(false)
1406         , m_lineLeadingFwdPt(0)
1407         , m_lineLeadingBackPt(0)
1408         , m_lineIntent(LineAnnotation::Unknown)
1409     {
1410     }
1411 
1412     void transform(const QTransform &matrix) override;
1413     void baseTransform(const QTransform &matrix) override;
1414     void resetTransformation() override;
1415     void translate(const NormalizedPoint &coord) override;
1416     double distanceSqr(double x, double y, double xScale, double yScale) const override;
1417     void setAnnotationProperties(const QDomNode &node) override;
1418     AnnotationPrivate *getNewAnnotationPrivate() override;
1419 
1420     QList<NormalizedPoint> m_linePoints;
1421     QList<NormalizedPoint> m_transformedLinePoints;
1422     LineAnnotation::TermStyle m_lineStartStyle;
1423     LineAnnotation::TermStyle m_lineEndStyle;
1424     bool m_lineClosed : 1;
1425     bool m_lineShowCaption : 1;
1426     QColor m_lineInnerColor;
1427     double m_lineLeadingFwdPt;
1428     double m_lineLeadingBackPt;
1429     LineAnnotation::LineIntent m_lineIntent;
1430 };
1431 
1432 LineAnnotation::LineAnnotation()
1433     : Annotation(*new LineAnnotationPrivate())
1434 {
1435 }
1436 
1437 LineAnnotation::LineAnnotation(const QDomNode &description)
1438     : Annotation(*new LineAnnotationPrivate(), description)
1439 {
1440 }
1441 
1442 LineAnnotation::~LineAnnotation()
1443 {
1444 }
1445 
1446 void LineAnnotation::setLinePoints(const QList<NormalizedPoint> &points)
1447 {
1448     Q_D(LineAnnotation);
1449     d->m_linePoints = points;
1450 }
1451 
1452 QList<NormalizedPoint> LineAnnotation::linePoints() const
1453 {
1454     Q_D(const LineAnnotation);
1455     return d->m_linePoints;
1456 }
1457 
1458 QList<NormalizedPoint> LineAnnotation::transformedLinePoints() const
1459 {
1460     Q_D(const LineAnnotation);
1461     return d->m_transformedLinePoints;
1462 }
1463 
1464 void LineAnnotation::setLineStartStyle(TermStyle style)
1465 {
1466     Q_D(LineAnnotation);
1467     d->m_lineStartStyle = style;
1468 }
1469 
1470 LineAnnotation::TermStyle LineAnnotation::lineStartStyle() const
1471 {
1472     Q_D(const LineAnnotation);
1473     return d->m_lineStartStyle;
1474 }
1475 
1476 void LineAnnotation::setLineEndStyle(TermStyle style)
1477 {
1478     Q_D(LineAnnotation);
1479     d->m_lineEndStyle = style;
1480 }
1481 
1482 LineAnnotation::TermStyle LineAnnotation::lineEndStyle() const
1483 {
1484     Q_D(const LineAnnotation);
1485     return d->m_lineEndStyle;
1486 }
1487 
1488 void LineAnnotation::setLineClosed(bool closed)
1489 {
1490     Q_D(LineAnnotation);
1491     d->m_lineClosed = closed;
1492 }
1493 
1494 bool LineAnnotation::lineClosed() const
1495 {
1496     Q_D(const LineAnnotation);
1497     return d->m_lineClosed;
1498 }
1499 
1500 void LineAnnotation::setLineInnerColor(const QColor &color)
1501 {
1502     Q_D(LineAnnotation);
1503     d->m_lineInnerColor = color;
1504 }
1505 
1506 QColor LineAnnotation::lineInnerColor() const
1507 {
1508     Q_D(const LineAnnotation);
1509     return d->m_lineInnerColor;
1510 }
1511 
1512 void LineAnnotation::setLineLeadingForwardPoint(double point)
1513 {
1514     Q_D(LineAnnotation);
1515     d->m_lineLeadingFwdPt = point;
1516 }
1517 
1518 double LineAnnotation::lineLeadingForwardPoint() const
1519 {
1520     Q_D(const LineAnnotation);
1521     return d->m_lineLeadingFwdPt;
1522 }
1523 
1524 void LineAnnotation::setLineLeadingBackwardPoint(double point)
1525 {
1526     Q_D(LineAnnotation);
1527     d->m_lineLeadingBackPt = point;
1528 }
1529 
1530 double LineAnnotation::lineLeadingBackwardPoint() const
1531 {
1532     Q_D(const LineAnnotation);
1533     return d->m_lineLeadingBackPt;
1534 }
1535 
1536 void LineAnnotation::setShowCaption(bool show)
1537 {
1538     Q_D(LineAnnotation);
1539     d->m_lineShowCaption = show;
1540 }
1541 
1542 bool LineAnnotation::showCaption() const
1543 {
1544     Q_D(const LineAnnotation);
1545     return d->m_lineShowCaption;
1546 }
1547 
1548 void LineAnnotation::setLineIntent(LineIntent intent)
1549 {
1550     Q_D(LineAnnotation);
1551     d->m_lineIntent = intent;
1552 }
1553 
1554 LineAnnotation::LineIntent LineAnnotation::lineIntent() const
1555 {
1556     Q_D(const LineAnnotation);
1557     return d->m_lineIntent;
1558 }
1559 
1560 Annotation::SubType LineAnnotation::subType() const
1561 {
1562     return ALine;
1563 }
1564 
1565 void LineAnnotation::store(QDomNode &node, QDomDocument &document) const
1566 {
1567     Q_D(const LineAnnotation);
1568     // recurse to parent objects storing properties
1569     Annotation::store(node, document);
1570 
1571     // create [line] element
1572     QDomElement lineElement = document.createElement(QStringLiteral("line"));
1573     node.appendChild(lineElement);
1574 
1575     // store the attributes
1576     if (d->m_lineStartStyle != None) {
1577         lineElement.setAttribute(QStringLiteral("startStyle"), (int)d->m_lineStartStyle);
1578     }
1579     if (d->m_lineEndStyle != None) {
1580         lineElement.setAttribute(QStringLiteral("endStyle"), (int)d->m_lineEndStyle);
1581     }
1582     if (d->m_lineClosed) {
1583         lineElement.setAttribute(QStringLiteral("closed"), d->m_lineClosed);
1584     }
1585     if (d->m_lineInnerColor.isValid()) {
1586         lineElement.setAttribute(QStringLiteral("innerColor"), d->m_lineInnerColor.name());
1587     }
1588     if (d->m_lineLeadingFwdPt != 0.0) {
1589         lineElement.setAttribute(QStringLiteral("leadFwd"), QString::number(d->m_lineLeadingFwdPt));
1590     }
1591     if (d->m_lineLeadingBackPt != 0.0) {
1592         lineElement.setAttribute(QStringLiteral("leadBack"), QString::number(d->m_lineLeadingBackPt));
1593     }
1594     if (d->m_lineShowCaption) {
1595         lineElement.setAttribute(QStringLiteral("showCaption"), d->m_lineShowCaption);
1596     }
1597     if (d->m_lineIntent != Unknown) {
1598         lineElement.setAttribute(QStringLiteral("intent"), d->m_lineIntent);
1599     }
1600 
1601     // append the list of points
1602     int points = d->m_linePoints.count();
1603     if (points > 1) {
1604         QList<NormalizedPoint>::const_iterator it = d->m_linePoints.begin(), end = d->m_linePoints.end();
1605         while (it != end) {
1606             const NormalizedPoint &p = *it;
1607             QDomElement pElement = document.createElement(QStringLiteral("point"));
1608             lineElement.appendChild(pElement);
1609             pElement.setAttribute(QStringLiteral("x"), QString::number(p.x));
1610             pElement.setAttribute(QStringLiteral("y"), QString::number(p.y));
1611             it++; // to avoid loop
1612         }
1613     }
1614 }
1615 
1616 void LineAnnotationPrivate::transform(const QTransform &matrix)
1617 {
1618     AnnotationPrivate::transform(matrix);
1619 
1620     QMutableListIterator<NormalizedPoint> it(m_transformedLinePoints);
1621     while (it.hasNext()) {
1622         it.next().transform(matrix);
1623     }
1624 }
1625 
1626 void LineAnnotationPrivate::baseTransform(const QTransform &matrix)
1627 {
1628     AnnotationPrivate::baseTransform(matrix);
1629 
1630     QMutableListIterator<NormalizedPoint> it(m_linePoints);
1631     while (it.hasNext()) {
1632         it.next().transform(matrix);
1633     }
1634 }
1635 
1636 void LineAnnotationPrivate::resetTransformation()
1637 {
1638     AnnotationPrivate::resetTransformation();
1639 
1640     m_transformedLinePoints = m_linePoints;
1641 }
1642 
1643 void LineAnnotationPrivate::translate(const NormalizedPoint &coord)
1644 {
1645     AnnotationPrivate::translate(coord);
1646 
1647     QMutableListIterator<NormalizedPoint> it(m_linePoints);
1648     while (it.hasNext()) {
1649         NormalizedPoint &p = it.next();
1650         p.x = p.x + coord.x;
1651         p.y = p.y + coord.y;
1652     }
1653 }
1654 
1655 void LineAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
1656 {
1657     Okular::AnnotationPrivate::setAnnotationProperties(node);
1658 
1659     // loop through the whole children looking for a 'line' element
1660     QDomNode subNode = node.firstChild();
1661     while (subNode.isElement()) {
1662         QDomElement e = subNode.toElement();
1663         subNode = subNode.nextSibling();
1664         if (e.tagName() != QLatin1String("line")) {
1665             continue;
1666         }
1667 
1668         // parse the attributes
1669         if (e.hasAttribute(QStringLiteral("startStyle"))) {
1670             m_lineStartStyle = (LineAnnotation::TermStyle)e.attribute(QStringLiteral("startStyle")).toInt();
1671         }
1672         if (e.hasAttribute(QStringLiteral("endStyle"))) {
1673             m_lineEndStyle = (LineAnnotation::TermStyle)e.attribute(QStringLiteral("endStyle")).toInt();
1674         }
1675         if (e.hasAttribute(QStringLiteral("closed"))) {
1676             m_lineClosed = e.attribute(QStringLiteral("closed")).toInt();
1677         }
1678         if (e.hasAttribute(QStringLiteral("innerColor"))) {
1679             m_lineInnerColor = QColor(e.attribute(QStringLiteral("innerColor")));
1680         }
1681         if (e.hasAttribute(QStringLiteral("leadFwd"))) {
1682             m_lineLeadingFwdPt = e.attribute(QStringLiteral("leadFwd")).toDouble();
1683         }
1684         if (e.hasAttribute(QStringLiteral("leadBack"))) {
1685             m_lineLeadingBackPt = e.attribute(QStringLiteral("leadBack")).toDouble();
1686         }
1687         if (e.hasAttribute(QStringLiteral("showCaption"))) {
1688             m_lineShowCaption = e.attribute(QStringLiteral("showCaption")).toInt();
1689         }
1690         if (e.hasAttribute(QStringLiteral("intent"))) {
1691             m_lineIntent = (LineAnnotation::LineIntent)e.attribute(QStringLiteral("intent")).toInt();
1692         }
1693 
1694         // parse all 'point' subnodes
1695         QDomNode pointNode = e.firstChild();
1696         while (pointNode.isElement()) {
1697             QDomElement pe = pointNode.toElement();
1698             pointNode = pointNode.nextSibling();
1699 
1700             if (pe.tagName() != QLatin1String("point")) {
1701                 continue;
1702             }
1703 
1704             NormalizedPoint p;
1705             p.x = pe.attribute(QStringLiteral("x"), QStringLiteral("0.0")).toDouble();
1706             p.y = pe.attribute(QStringLiteral("y"), QStringLiteral("0.0")).toDouble();
1707             m_linePoints.append(p);
1708         }
1709 
1710         // loading complete
1711         break;
1712     }
1713 
1714     m_transformedLinePoints = m_linePoints;
1715 }
1716 
1717 AnnotationPrivate *LineAnnotationPrivate::getNewAnnotationPrivate()
1718 {
1719     return new LineAnnotationPrivate();
1720 }
1721 
1722 double LineAnnotationPrivate::distanceSqr(double x, double y, double xScale, double yScale) const
1723 {
1724     QList<NormalizedPoint> transformedLinePoints = m_transformedLinePoints;
1725 
1726     if (m_lineClosed) { // Close the path
1727         transformedLinePoints.append(transformedLinePoints.first());
1728     }
1729 
1730     if (m_lineInnerColor.isValid()) {
1731         QPolygonF polygon;
1732         for (const NormalizedPoint &p : std::as_const(transformedLinePoints)) {
1733             polygon.append(QPointF(p.x, p.y));
1734         }
1735 
1736         if (polygon.containsPoint(QPointF(x, y), Qt::WindingFill)) {
1737             return 0;
1738         }
1739     }
1740 
1741     return strokeDistance(::distanceSqr(x, y, xScale, yScale, transformedLinePoints), m_style.width() * xScale / (m_page->m_width * 2));
1742 }
1743 
1744 /** GeomAnnotation [Annotation] */
1745 
1746 class Okular::GeomAnnotationPrivate : public Okular::AnnotationPrivate
1747 {
1748 public:
1749     GeomAnnotationPrivate()
1750         : AnnotationPrivate()
1751         , m_geomType(GeomAnnotation::InscribedSquare)
1752     {
1753     }
1754     void setAnnotationProperties(const QDomNode &node) override;
1755     bool canBeResized() const override;
1756     AnnotationPrivate *getNewAnnotationPrivate() override;
1757     double distanceSqr(double x, double y, double xScale, double yScale) const override;
1758 
1759     GeomAnnotation::GeomType m_geomType;
1760     QColor m_geomInnerColor;
1761 };
1762 
1763 GeomAnnotation::GeomAnnotation()
1764     : Annotation(*new GeomAnnotationPrivate())
1765 {
1766 }
1767 
1768 GeomAnnotation::GeomAnnotation(const QDomNode &description)
1769     : Annotation(*new GeomAnnotationPrivate(), description)
1770 {
1771 }
1772 
1773 GeomAnnotation::~GeomAnnotation()
1774 {
1775 }
1776 
1777 void GeomAnnotation::setGeometricalType(GeomType type)
1778 {
1779     Q_D(GeomAnnotation);
1780     d->m_geomType = type;
1781 }
1782 
1783 GeomAnnotation::GeomType GeomAnnotation::geometricalType() const
1784 {
1785     Q_D(const GeomAnnotation);
1786     return d->m_geomType;
1787 }
1788 
1789 void GeomAnnotation::setGeometricalInnerColor(const QColor &color)
1790 {
1791     Q_D(GeomAnnotation);
1792     d->m_geomInnerColor = color;
1793 }
1794 
1795 QColor GeomAnnotation::geometricalInnerColor() const
1796 {
1797     Q_D(const GeomAnnotation);
1798     return d->m_geomInnerColor;
1799 }
1800 
1801 Annotation::SubType GeomAnnotation::subType() const
1802 {
1803     return AGeom;
1804 }
1805 
1806 void GeomAnnotation::store(QDomNode &node, QDomDocument &document) const
1807 {
1808     Q_D(const GeomAnnotation);
1809     // recurse to parent objects storing properties
1810     Annotation::store(node, document);
1811 
1812     // create [geom] element
1813     QDomElement geomElement = document.createElement(QStringLiteral("geom"));
1814     node.appendChild(geomElement);
1815 
1816     // append the optional attributes
1817     if (d->m_geomType != InscribedSquare) {
1818         geomElement.setAttribute(QStringLiteral("type"), (int)d->m_geomType);
1819     }
1820     if (d->m_geomInnerColor.isValid()) {
1821         geomElement.setAttribute(QStringLiteral("color"), d->m_geomInnerColor.name());
1822     }
1823 }
1824 
1825 void GeomAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
1826 {
1827     Okular::AnnotationPrivate::setAnnotationProperties(node);
1828     // loop through the whole children looking for a 'geom' element
1829     QDomNode subNode = node.firstChild();
1830     while (subNode.isElement()) {
1831         QDomElement e = subNode.toElement();
1832         subNode = subNode.nextSibling();
1833         if (e.tagName() != QLatin1String("geom")) {
1834             continue;
1835         }
1836 
1837         // parse the attributes
1838         if (e.hasAttribute(QStringLiteral("type"))) {
1839             m_geomType = (GeomAnnotation::GeomType)e.attribute(QStringLiteral("type")).toInt();
1840         }
1841         if (e.hasAttribute(QStringLiteral("color"))) {
1842             m_geomInnerColor = QColor(e.attribute(QStringLiteral("color")));
1843         }
1844         // compatibility
1845         if (e.hasAttribute(QStringLiteral("width"))) {
1846             m_style.setWidth(e.attribute(QStringLiteral("width")).toInt());
1847         }
1848 
1849         // loading complete
1850         break;
1851     }
1852 }
1853 
1854 bool GeomAnnotationPrivate::canBeResized() const
1855 {
1856     return true;
1857 }
1858 
1859 AnnotationPrivate *GeomAnnotationPrivate::getNewAnnotationPrivate()
1860 {
1861     return new GeomAnnotationPrivate();
1862 }
1863 
1864 double GeomAnnotationPrivate::distanceSqr(double x, double y, double xScale, double yScale) const
1865 {
1866     double distance = 0;
1867     // the line thickness is applied unevenly (only on the "inside") - account for this
1868     bool withinShape = false;
1869     switch (m_geomType) {
1870     case GeomAnnotation::InscribedCircle: {
1871         // calculate the center point and focus lengths of the ellipse
1872         const double centerX = (m_transformedBoundary.left + m_transformedBoundary.right) / 2.0;
1873         const double centerY = (m_transformedBoundary.top + m_transformedBoundary.bottom) / 2.0;
1874         const double focusX = (m_transformedBoundary.right - centerX);
1875         const double focusY = (m_transformedBoundary.bottom - centerY);
1876 
1877         const double focusXSqr = pow(focusX, 2);
1878         const double focusYSqr = pow(focusY, 2);
1879 
1880         // to calculate the distance from the ellipse, we will first find the point "projection"
1881         // that lies on the ellipse and is closest to the point (x,y)
1882         // This point can obviously be written as "center + lambda(inputPoint - center)".
1883         // Because the point lies on the ellipse, we know that:
1884         //   1 = ((center.x - projection.x)/focusX)^2 + ((center.y - projection.y)/focusY)^2
1885         // After filling in projection.x = center.x + lambda * (inputPoint.x - center.x)
1886         // and its y-equivalent, we can solve for lambda:
1887         const double lambda = sqrt(focusXSqr * focusYSqr / (focusYSqr * pow(x - centerX, 2) + focusXSqr * pow(y - centerY, 2)));
1888 
1889         // if the ellipse is filled, we treat all points within as "on" it
1890         if (lambda > 1) {
1891             if (m_geomInnerColor.isValid()) {
1892                 return 0;
1893             } else {
1894                 withinShape = true;
1895             }
1896         }
1897 
1898         // otherwise we calculate the squared distance from the projected point on the ellipse
1899         NormalizedPoint projection(centerX, centerY);
1900         projection.x += lambda * (x - centerX);
1901         projection.y += lambda * (y - centerY);
1902 
1903         distance = projection.distanceSqr(x, y, xScale, yScale);
1904         break;
1905     }
1906 
1907     case GeomAnnotation::InscribedSquare:
1908         // if the square is filled, only check the bounding box
1909         if (m_geomInnerColor.isValid()) {
1910             return AnnotationPrivate::distanceSqr(x, y, xScale, yScale);
1911         }
1912 
1913         const QList<NormalizedPoint> edges = {NormalizedPoint(m_transformedBoundary.left, m_transformedBoundary.top),
1914                                               NormalizedPoint(m_transformedBoundary.right, m_transformedBoundary.top),
1915                                               NormalizedPoint(m_transformedBoundary.right, m_transformedBoundary.bottom),
1916                                               NormalizedPoint(m_transformedBoundary.left, m_transformedBoundary.bottom),
1917                                               NormalizedPoint(m_transformedBoundary.left, m_transformedBoundary.top)};
1918         distance = ::distanceSqr(x, y, xScale, yScale, edges);
1919 
1920         if (m_transformedBoundary.contains(x, y)) {
1921             withinShape = true;
1922         }
1923 
1924         break;
1925     }
1926     if (withinShape) {
1927         distance = strokeDistance(distance, m_style.width() * xScale / m_page->m_width);
1928     }
1929 
1930     return distance;
1931 }
1932 
1933 /** HighlightAnnotation [Annotation] */
1934 
1935 class HighlightAnnotation::Quad::Private
1936 {
1937 public:
1938     Private()
1939         : m_capStart(false)
1940         , m_capEnd(false)
1941         , m_feather(0.0)
1942     {
1943     }
1944 
1945     NormalizedPoint m_points[4];
1946     NormalizedPoint m_transformedPoints[4];
1947     bool m_capStart : 1;
1948     bool m_capEnd : 1;
1949     double m_feather;
1950 };
1951 
1952 HighlightAnnotation::Quad::Quad()
1953     : d(new Private)
1954 {
1955 }
1956 
1957 HighlightAnnotation::Quad::~Quad()
1958 {
1959     delete d;
1960 }
1961 
1962 HighlightAnnotation::Quad::Quad(const Quad &other)
1963     : d(new Private)
1964 {
1965     *d = *other.d;
1966 }
1967 
1968 HighlightAnnotation::Quad &HighlightAnnotation::Quad::operator=(const Quad &other)
1969 {
1970     if (this != &other) {
1971         *d = *other.d;
1972     }
1973 
1974     return *this;
1975 }
1976 
1977 void HighlightAnnotation::Quad::setPoint(const NormalizedPoint &point, int index)
1978 {
1979     if (index < 0 || index > 3) {
1980         return;
1981     }
1982 
1983     d->m_points[index] = point;
1984 }
1985 
1986 NormalizedPoint HighlightAnnotation::Quad::point(int index) const
1987 {
1988     if (index < 0 || index > 3) {
1989         return NormalizedPoint();
1990     }
1991 
1992     return d->m_points[index];
1993 }
1994 
1995 NormalizedPoint HighlightAnnotation::Quad::transformedPoint(int index) const
1996 {
1997     if (index < 0 || index > 3) {
1998         return NormalizedPoint();
1999     }
2000 
2001     return d->m_transformedPoints[index];
2002 }
2003 
2004 void HighlightAnnotation::Quad::setCapStart(bool value)
2005 {
2006     d->m_capStart = value;
2007 }
2008 
2009 bool HighlightAnnotation::Quad::capStart() const
2010 {
2011     return d->m_capStart;
2012 }
2013 
2014 void HighlightAnnotation::Quad::setCapEnd(bool value)
2015 {
2016     d->m_capEnd = value;
2017 }
2018 
2019 bool HighlightAnnotation::Quad::capEnd() const
2020 {
2021     return d->m_capEnd;
2022 }
2023 
2024 void HighlightAnnotation::Quad::setFeather(double width)
2025 {
2026     d->m_feather = width;
2027 }
2028 
2029 double HighlightAnnotation::Quad::feather() const
2030 {
2031     return d->m_feather;
2032 }
2033 
2034 void HighlightAnnotation::Quad::transform(const QTransform &matrix)
2035 {
2036     for (int i = 0; i < 4; ++i) {
2037         d->m_transformedPoints[i] = d->m_points[i];
2038         d->m_transformedPoints[i].transform(matrix);
2039     }
2040 }
2041 
2042 class Okular::HighlightAnnotationPrivate : public Okular::AnnotationPrivate
2043 {
2044 public:
2045     HighlightAnnotationPrivate()
2046         : AnnotationPrivate()
2047         , m_highlightType(HighlightAnnotation::Highlight)
2048     {
2049     }
2050 
2051     void transform(const QTransform &matrix) override;
2052     void baseTransform(const QTransform &matrix) override;
2053     double distanceSqr(double x, double y, double xScale, double yScale) const override;
2054     void setAnnotationProperties(const QDomNode &node) override;
2055     AnnotationPrivate *getNewAnnotationPrivate() override;
2056 
2057     HighlightAnnotation::HighlightType m_highlightType;
2058     QList<HighlightAnnotation::Quad> m_highlightQuads;
2059 };
2060 
2061 HighlightAnnotation::HighlightAnnotation()
2062     : Annotation(*new HighlightAnnotationPrivate())
2063 {
2064 }
2065 
2066 HighlightAnnotation::HighlightAnnotation(const QDomNode &description)
2067     : Annotation(*new HighlightAnnotationPrivate(), description)
2068 {
2069 }
2070 
2071 HighlightAnnotation::~HighlightAnnotation()
2072 {
2073 }
2074 
2075 void HighlightAnnotation::setHighlightType(HighlightType type)
2076 {
2077     Q_D(HighlightAnnotation);
2078     d->m_highlightType = type;
2079 }
2080 
2081 HighlightAnnotation::HighlightType HighlightAnnotation::highlightType() const
2082 {
2083     Q_D(const HighlightAnnotation);
2084     return d->m_highlightType;
2085 }
2086 
2087 QList<HighlightAnnotation::Quad> &HighlightAnnotation::highlightQuads()
2088 {
2089     Q_D(HighlightAnnotation);
2090     return d->m_highlightQuads;
2091 }
2092 
2093 const QList<HighlightAnnotation::Quad> &HighlightAnnotation::highlightQuads() const
2094 {
2095     Q_D(const HighlightAnnotation);
2096     return d->m_highlightQuads;
2097 }
2098 
2099 void HighlightAnnotation::store(QDomNode &node, QDomDocument &document) const
2100 {
2101     Q_D(const HighlightAnnotation);
2102     // recurse to parent objects storing properties
2103     Annotation::store(node, document);
2104 
2105     // create [hl] element
2106     QDomElement hlElement = document.createElement(QStringLiteral("hl"));
2107     node.appendChild(hlElement);
2108 
2109     // append the optional attributes
2110     if (d->m_highlightType != Highlight) {
2111         hlElement.setAttribute(QStringLiteral("type"), (int)d->m_highlightType);
2112     }
2113     if (d->m_highlightQuads.count() < 1) {
2114         return;
2115     }
2116     // append highlight quads, all children describe quads
2117     QList<Quad>::const_iterator it = d->m_highlightQuads.begin(), end = d->m_highlightQuads.end();
2118     for (; it != end; ++it) {
2119         QDomElement quadElement = document.createElement(QStringLiteral("quad"));
2120         hlElement.appendChild(quadElement);
2121         const Quad &q = *it;
2122         quadElement.setAttribute(QStringLiteral("ax"), QString::number(q.point(0).x));
2123         quadElement.setAttribute(QStringLiteral("ay"), QString::number(q.point(0).y));
2124         quadElement.setAttribute(QStringLiteral("bx"), QString::number(q.point(1).x));
2125         quadElement.setAttribute(QStringLiteral("by"), QString::number(q.point(1).y));
2126         quadElement.setAttribute(QStringLiteral("cx"), QString::number(q.point(2).x));
2127         quadElement.setAttribute(QStringLiteral("cy"), QString::number(q.point(2).y));
2128         quadElement.setAttribute(QStringLiteral("dx"), QString::number(q.point(3).x));
2129         quadElement.setAttribute(QStringLiteral("dy"), QString::number(q.point(3).y));
2130         if (q.capStart()) {
2131             quadElement.setAttribute(QStringLiteral("start"), 1);
2132         }
2133         if (q.capEnd()) {
2134             quadElement.setAttribute(QStringLiteral("end"), 1);
2135         }
2136         quadElement.setAttribute(QStringLiteral("feather"), QString::number(q.feather()));
2137     }
2138 }
2139 
2140 Annotation::SubType HighlightAnnotation::subType() const
2141 {
2142     return AHighlight;
2143 }
2144 
2145 void HighlightAnnotationPrivate::transform(const QTransform &matrix)
2146 {
2147     AnnotationPrivate::transform(matrix);
2148 
2149     QMutableListIterator<HighlightAnnotation::Quad> it(m_highlightQuads);
2150     while (it.hasNext()) {
2151         it.next().transform(matrix);
2152     }
2153 }
2154 
2155 void HighlightAnnotationPrivate::baseTransform(const QTransform &matrix)
2156 {
2157     AnnotationPrivate::baseTransform(matrix);
2158 
2159     QMutableListIterator<HighlightAnnotation::Quad> it(m_highlightQuads);
2160     while (it.hasNext()) {
2161         it.next().transform(matrix);
2162     }
2163 }
2164 
2165 void HighlightAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
2166 {
2167     Okular::AnnotationPrivate::setAnnotationProperties(node);
2168     m_highlightQuads.clear();
2169 
2170     // loop through the whole children looking for a 'hl' element
2171     QDomNode subNode = node.firstChild();
2172     while (subNode.isElement()) {
2173         QDomElement e = subNode.toElement();
2174         subNode = subNode.nextSibling();
2175         if (e.tagName() != QLatin1String("hl")) {
2176             continue;
2177         }
2178 
2179         // parse the attributes
2180         if (e.hasAttribute(QStringLiteral("type"))) {
2181             m_highlightType = (HighlightAnnotation::HighlightType)e.attribute(QStringLiteral("type")).toInt();
2182         }
2183 
2184         // parse all 'quad' subnodes
2185         QDomNode quadNode = e.firstChild();
2186         for (; quadNode.isElement(); quadNode = quadNode.nextSibling()) {
2187             QDomElement qe = quadNode.toElement();
2188             if (qe.tagName() != QLatin1String("quad")) {
2189                 continue;
2190             }
2191 
2192             HighlightAnnotation::Quad q;
2193             q.setPoint(NormalizedPoint(qe.attribute(QStringLiteral("ax"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("ay"), QStringLiteral("0.0")).toDouble()), 0);
2194             q.setPoint(NormalizedPoint(qe.attribute(QStringLiteral("bx"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("by"), QStringLiteral("0.0")).toDouble()), 1);
2195             q.setPoint(NormalizedPoint(qe.attribute(QStringLiteral("cx"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("cy"), QStringLiteral("0.0")).toDouble()), 2);
2196             q.setPoint(NormalizedPoint(qe.attribute(QStringLiteral("dx"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("dy"), QStringLiteral("0.0")).toDouble()), 3);
2197             q.setCapStart(qe.hasAttribute(QStringLiteral("start")));
2198             q.setCapEnd(qe.hasAttribute(QStringLiteral("end")));
2199             q.setFeather(qe.attribute(QStringLiteral("feather"), QStringLiteral("0.1")).toDouble());
2200 
2201             q.transform(QTransform());
2202 
2203             m_highlightQuads.append(q);
2204         }
2205 
2206         // loading complete
2207         break;
2208     }
2209 }
2210 
2211 AnnotationPrivate *HighlightAnnotationPrivate::getNewAnnotationPrivate()
2212 {
2213     return new HighlightAnnotationPrivate();
2214 }
2215 
2216 double HighlightAnnotationPrivate::distanceSqr(double x, double y, double xScale, double yScale) const
2217 {
2218     NormalizedPoint point(x, y);
2219     double outsideDistance = DBL_MAX;
2220     for (const HighlightAnnotation::Quad &quad : m_highlightQuads) {
2221         QList<NormalizedPoint> pathPoints;
2222 
2223         // first, we check if the point is within the area described by the 4 quads
2224         // this is the case, if the point is always on one side of each segments delimiting the polygon:
2225         pathPoints << quad.transformedPoint(0);
2226         int directionVote = 0;
2227         for (int i = 1; i < 5; ++i) {
2228             NormalizedPoint thisPoint = quad.transformedPoint(i % 4);
2229             directionVote += (isLeftOfVector(pathPoints.back(), thisPoint, point)) ? 1 : -1;
2230             pathPoints << thisPoint;
2231         }
2232         if (abs(directionVote) == 4) {
2233             return 0;
2234         }
2235 
2236         // if that's not the case, we treat the outline as path and simply determine
2237         // the distance from the path to the point
2238         const double thisOutsideDistance = ::distanceSqr(x, y, xScale, yScale, pathPoints);
2239         if (thisOutsideDistance < outsideDistance) {
2240             outsideDistance = thisOutsideDistance;
2241         }
2242     }
2243 
2244     return outsideDistance;
2245 }
2246 
2247 /** StampAnnotation [Annotation] */
2248 
2249 class Okular::StampAnnotationPrivate : public Okular::AnnotationPrivate
2250 {
2251 public:
2252     StampAnnotationPrivate()
2253         : AnnotationPrivate()
2254         , m_stampIconName(QStringLiteral("Draft"))
2255     {
2256     }
2257     void setAnnotationProperties(const QDomNode &node) override;
2258     bool canBeResized() const override;
2259     AnnotationPrivate *getNewAnnotationPrivate() override;
2260 
2261     QString m_stampIconName;
2262 };
2263 
2264 StampAnnotation::StampAnnotation()
2265     : Annotation(*new StampAnnotationPrivate())
2266 {
2267 }
2268 
2269 StampAnnotation::StampAnnotation(const QDomNode &description)
2270     : Annotation(*new StampAnnotationPrivate(), description)
2271 {
2272 }
2273 
2274 StampAnnotation::~StampAnnotation()
2275 {
2276 }
2277 
2278 void StampAnnotation::setStampIconName(const QString &name)
2279 {
2280     Q_D(StampAnnotation);
2281     d->m_stampIconName = name;
2282 }
2283 
2284 QString StampAnnotation::stampIconName() const
2285 {
2286     Q_D(const StampAnnotation);
2287     return d->m_stampIconName;
2288 }
2289 
2290 Annotation::SubType StampAnnotation::subType() const
2291 {
2292     return AStamp;
2293 }
2294 
2295 void StampAnnotation::store(QDomNode &node, QDomDocument &document) const
2296 {
2297     Q_D(const StampAnnotation);
2298     // recurse to parent objects storing properties
2299     Annotation::store(node, document);
2300 
2301     // create [stamp] element
2302     QDomElement stampElement = document.createElement(QStringLiteral("stamp"));
2303     node.appendChild(stampElement);
2304 
2305     // append the optional attributes
2306     if (d->m_stampIconName != QLatin1String("Draft")) {
2307         stampElement.setAttribute(QStringLiteral("icon"), d->m_stampIconName);
2308     }
2309 }
2310 
2311 void StampAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
2312 {
2313     Okular::AnnotationPrivate::setAnnotationProperties(node);
2314 
2315     // loop through the whole children looking for a 'stamp' element
2316     QDomNode subNode = node.firstChild();
2317     while (subNode.isElement()) {
2318         QDomElement e = subNode.toElement();
2319         subNode = subNode.nextSibling();
2320         if (e.tagName() != QLatin1String("stamp")) {
2321             continue;
2322         }
2323 
2324         // parse the attributes
2325         if (e.hasAttribute(QStringLiteral("icon"))) {
2326             m_stampIconName = e.attribute(QStringLiteral("icon"));
2327         }
2328 
2329         // loading complete
2330         break;
2331     }
2332 }
2333 
2334 bool StampAnnotationPrivate::canBeResized() const
2335 {
2336     return true;
2337 }
2338 
2339 AnnotationPrivate *StampAnnotationPrivate::getNewAnnotationPrivate()
2340 {
2341     return new StampAnnotationPrivate();
2342 }
2343 
2344 /** InkAnnotation [Annotation] */
2345 
2346 class Okular::InkAnnotationPrivate : public Okular::AnnotationPrivate
2347 {
2348 public:
2349     InkAnnotationPrivate()
2350         : AnnotationPrivate()
2351     {
2352     }
2353 
2354     void transform(const QTransform &matrix) override;
2355     void baseTransform(const QTransform &matrix) override;
2356     void resetTransformation() override;
2357     double distanceSqr(double x, double y, double xScale, double yScale) const override;
2358     void translate(const NormalizedPoint &coord) override;
2359     void setAnnotationProperties(const QDomNode &node) override;
2360     AnnotationPrivate *getNewAnnotationPrivate() override;
2361 
2362     QList<QList<NormalizedPoint>> m_inkPaths;
2363     QList<QList<NormalizedPoint>> m_transformedInkPaths;
2364 };
2365 
2366 InkAnnotation::InkAnnotation()
2367     : Annotation(*new InkAnnotationPrivate())
2368 {
2369 }
2370 
2371 InkAnnotation::InkAnnotation(const QDomNode &description)
2372     : Annotation(*new InkAnnotationPrivate(), description)
2373 {
2374 }
2375 
2376 InkAnnotation::~InkAnnotation()
2377 {
2378 }
2379 
2380 void InkAnnotation::setInkPaths(const QList<QList<NormalizedPoint>> &paths)
2381 {
2382     Q_D(InkAnnotation);
2383     d->m_inkPaths = paths;
2384 }
2385 
2386 QList<QList<NormalizedPoint>> InkAnnotation::inkPaths() const
2387 {
2388     Q_D(const InkAnnotation);
2389     return d->m_inkPaths;
2390 }
2391 
2392 QList<QList<NormalizedPoint>> InkAnnotation::transformedInkPaths() const
2393 {
2394     Q_D(const InkAnnotation);
2395     return d->m_transformedInkPaths;
2396 }
2397 
2398 Annotation::SubType InkAnnotation::subType() const
2399 {
2400     return AInk;
2401 }
2402 
2403 void InkAnnotation::store(QDomNode &node, QDomDocument &document) const
2404 {
2405     Q_D(const InkAnnotation);
2406     // recurse to parent objects storing properties
2407     Annotation::store(node, document);
2408 
2409     // create [ink] element
2410     QDomElement inkElement = document.createElement(QStringLiteral("ink"));
2411     node.appendChild(inkElement);
2412 
2413     // append the optional attributes
2414     if (d->m_inkPaths.count() < 1) {
2415         return;
2416     }
2417 
2418     QList<QList<NormalizedPoint>>::const_iterator pIt = d->m_inkPaths.begin(), pEnd = d->m_inkPaths.end();
2419     for (; pIt != pEnd; ++pIt) {
2420         QDomElement pathElement = document.createElement(QStringLiteral("path"));
2421         inkElement.appendChild(pathElement);
2422         const QList<NormalizedPoint> &path = *pIt;
2423         for (const NormalizedPoint &point : path) {
2424             QDomElement pointElement = document.createElement(QStringLiteral("point"));
2425             pathElement.appendChild(pointElement);
2426             pointElement.setAttribute(QStringLiteral("x"), QString::number(point.x));
2427             pointElement.setAttribute(QStringLiteral("y"), QString::number(point.y));
2428         }
2429     }
2430 }
2431 
2432 double InkAnnotationPrivate::distanceSqr(double x, double y, double xScale, double yScale) const
2433 {
2434     double distance = DBL_MAX;
2435     for (const QList<NormalizedPoint> &path : m_transformedInkPaths) {
2436         const double thisDistance = ::distanceSqr(x, y, xScale, yScale, path);
2437         if (thisDistance < distance) {
2438             distance = thisDistance;
2439         }
2440     }
2441     return strokeDistance(distance, m_style.width() * xScale / (m_page->m_width * 2));
2442 }
2443 
2444 void InkAnnotationPrivate::transform(const QTransform &matrix)
2445 {
2446     AnnotationPrivate::transform(matrix);
2447 
2448     for (int i = 0; i < m_transformedInkPaths.count(); ++i) {
2449         QMutableListIterator<NormalizedPoint> it(m_transformedInkPaths[i]);
2450         while (it.hasNext()) {
2451             it.next().transform(matrix);
2452         }
2453     }
2454 }
2455 
2456 void InkAnnotationPrivate::baseTransform(const QTransform &matrix)
2457 {
2458     AnnotationPrivate::baseTransform(matrix);
2459 
2460     for (int i = 0; i < m_inkPaths.count(); ++i) {
2461         QMutableListIterator<NormalizedPoint> it(m_inkPaths[i]);
2462         while (it.hasNext()) {
2463             it.next().transform(matrix);
2464         }
2465     }
2466 }
2467 
2468 void InkAnnotationPrivate::resetTransformation()
2469 {
2470     AnnotationPrivate::resetTransformation();
2471 
2472     m_transformedInkPaths = m_inkPaths;
2473 }
2474 
2475 void InkAnnotationPrivate::translate(const NormalizedPoint &coord)
2476 {
2477     AnnotationPrivate::translate(coord);
2478 
2479     for (int i = 0; i < m_inkPaths.count(); ++i) {
2480         QMutableListIterator<NormalizedPoint> it(m_inkPaths[i]);
2481         while (it.hasNext()) {
2482             NormalizedPoint &p = it.next();
2483             p.x = p.x + coord.x;
2484             p.y = p.y + coord.y;
2485         }
2486     }
2487 }
2488 
2489 void InkAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
2490 {
2491     Okular::AnnotationPrivate::setAnnotationProperties(node);
2492     m_inkPaths.clear();
2493 
2494     // loop through the whole children looking for a 'ink' element
2495     QDomNode subNode = node.firstChild();
2496     while (subNode.isElement()) {
2497         QDomElement e = subNode.toElement();
2498         subNode = subNode.nextSibling();
2499         if (e.tagName() != QLatin1String("ink")) {
2500             continue;
2501         }
2502 
2503         // parse the 'path' subnodes
2504         QDomNode pathNode = e.firstChild();
2505         while (pathNode.isElement()) {
2506             QDomElement pathElement = pathNode.toElement();
2507             pathNode = pathNode.nextSibling();
2508 
2509             if (pathElement.tagName() != QLatin1String("path")) {
2510                 continue;
2511             }
2512 
2513             // build each path parsing 'point' subnodes
2514             QList<NormalizedPoint> path;
2515             QDomNode pointNode = pathElement.firstChild();
2516             while (pointNode.isElement()) {
2517                 QDomElement pointElement = pointNode.toElement();
2518                 pointNode = pointNode.nextSibling();
2519 
2520                 if (pointElement.tagName() != QLatin1String("point")) {
2521                     continue;
2522                 }
2523 
2524                 NormalizedPoint p;
2525                 p.x = pointElement.attribute(QStringLiteral("x"), QStringLiteral("0.0")).toDouble();
2526                 p.y = pointElement.attribute(QStringLiteral("y"), QStringLiteral("0.0")).toDouble();
2527                 path.append(p);
2528             }
2529 
2530             // add the path to the path list if it contains at least 2 nodes
2531             if (path.count() >= 2) {
2532                 m_inkPaths.append(path);
2533             }
2534         }
2535 
2536         // loading complete
2537         break;
2538     }
2539 
2540     m_transformedInkPaths = m_inkPaths;
2541 }
2542 
2543 AnnotationPrivate *InkAnnotationPrivate::getNewAnnotationPrivate()
2544 {
2545     return new InkAnnotationPrivate();
2546 }
2547 
2548 /** CaretAnnotation [Annotation] */
2549 
2550 class Okular::CaretAnnotationPrivate : public Okular::AnnotationPrivate
2551 {
2552 public:
2553     CaretAnnotationPrivate()
2554         : AnnotationPrivate()
2555         , m_symbol(CaretAnnotation::None)
2556     {
2557     }
2558 
2559     void setAnnotationProperties(const QDomNode &node) override;
2560     AnnotationPrivate *getNewAnnotationPrivate() override;
2561 
2562     CaretAnnotation::CaretSymbol m_symbol;
2563 };
2564 
2565 static QString caretSymbolToString(CaretAnnotation::CaretSymbol symbol)
2566 {
2567     switch (symbol) {
2568     case CaretAnnotation::None:
2569         return QStringLiteral("None");
2570     case CaretAnnotation::P:
2571         return QStringLiteral("P");
2572     }
2573     return QString();
2574 }
2575 
2576 static CaretAnnotation::CaretSymbol caretSymbolFromString(const QString &symbol)
2577 {
2578     if (symbol == QLatin1String("None")) {
2579         return CaretAnnotation::None;
2580     } else if (symbol == QLatin1String("P")) {
2581         return CaretAnnotation::P;
2582     }
2583     return CaretAnnotation::None;
2584 }
2585 
2586 void CaretAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
2587 {
2588     Okular::AnnotationPrivate::setAnnotationProperties(node);
2589 
2590     // loop through the whole children looking for a 'caret' element
2591     QDomNode subNode = node.firstChild();
2592     while (subNode.isElement()) {
2593         QDomElement e = subNode.toElement();
2594         subNode = subNode.nextSibling();
2595         if (e.tagName() != QLatin1String("caret")) {
2596             continue;
2597         }
2598 
2599         // parse the attributes
2600         if (e.hasAttribute(QStringLiteral("symbol"))) {
2601             m_symbol = caretSymbolFromString(e.attribute(QStringLiteral("symbol")));
2602         }
2603 
2604         // loading complete
2605         break;
2606     }
2607 }
2608 
2609 AnnotationPrivate *CaretAnnotationPrivate::getNewAnnotationPrivate()
2610 {
2611     return new CaretAnnotationPrivate();
2612 }
2613 
2614 CaretAnnotation::CaretAnnotation()
2615     : Annotation(*new CaretAnnotationPrivate())
2616 {
2617 }
2618 
2619 CaretAnnotation::CaretAnnotation(const QDomNode &description)
2620     : Annotation(*new CaretAnnotationPrivate(), description)
2621 {
2622 }
2623 
2624 CaretAnnotation::~CaretAnnotation()
2625 {
2626 }
2627 
2628 void CaretAnnotation::setCaretSymbol(CaretAnnotation::CaretSymbol symbol)
2629 {
2630     Q_D(CaretAnnotation);
2631     d->m_symbol = symbol;
2632 }
2633 
2634 CaretAnnotation::CaretSymbol CaretAnnotation::caretSymbol() const
2635 {
2636     Q_D(const CaretAnnotation);
2637     return d->m_symbol;
2638 }
2639 
2640 Annotation::SubType CaretAnnotation::subType() const
2641 {
2642     return ACaret;
2643 }
2644 
2645 void CaretAnnotation::store(QDomNode &node, QDomDocument &document) const
2646 {
2647     Q_D(const CaretAnnotation);
2648     // recurse to parent objects storing properties
2649     Annotation::store(node, document);
2650 
2651     // create [caret] element
2652     QDomElement caretElement = document.createElement(QStringLiteral("caret"));
2653     node.appendChild(caretElement);
2654 
2655     // append the optional attributes
2656     if (d->m_symbol != None) {
2657         caretElement.setAttribute(QStringLiteral("symbol"), caretSymbolToString(d->m_symbol));
2658     }
2659 }
2660 
2661 /** FileAttachmentAnnotation [Annotation] */
2662 
2663 class Okular::FileAttachmentAnnotationPrivate : public Okular::AnnotationPrivate
2664 {
2665 public:
2666     FileAttachmentAnnotationPrivate()
2667         : AnnotationPrivate()
2668         , icon(QStringLiteral("PushPin"))
2669         , embfile(nullptr)
2670     {
2671     }
2672     ~FileAttachmentAnnotationPrivate() override
2673     {
2674         delete embfile;
2675     }
2676 
2677     void setAnnotationProperties(const QDomNode &node) override;
2678     AnnotationPrivate *getNewAnnotationPrivate() override;
2679 
2680     // data fields
2681     QString icon;
2682     EmbeddedFile *embfile;
2683 };
2684 
2685 void FileAttachmentAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
2686 {
2687     Okular::AnnotationPrivate::setAnnotationProperties(node);
2688 
2689     // loop through the whole children looking for a 'fileattachment' element
2690     QDomNode subNode = node.firstChild();
2691     while (subNode.isElement()) {
2692         QDomElement e = subNode.toElement();
2693         subNode = subNode.nextSibling();
2694         if (e.tagName() != QLatin1String("fileattachment")) {
2695             continue;
2696         }
2697 
2698         // loading complete
2699         break;
2700     }
2701 }
2702 
2703 AnnotationPrivate *FileAttachmentAnnotationPrivate::getNewAnnotationPrivate()
2704 {
2705     return new FileAttachmentAnnotationPrivate();
2706 }
2707 
2708 FileAttachmentAnnotation::FileAttachmentAnnotation()
2709     : Annotation(*new FileAttachmentAnnotationPrivate())
2710 {
2711 }
2712 
2713 FileAttachmentAnnotation::FileAttachmentAnnotation(const QDomNode &description)
2714     : Annotation(*new FileAttachmentAnnotationPrivate(), description)
2715 {
2716 }
2717 
2718 FileAttachmentAnnotation::~FileAttachmentAnnotation()
2719 {
2720 }
2721 
2722 void FileAttachmentAnnotation::store(QDomNode &node, QDomDocument &document) const
2723 {
2724     // recurse to parent objects storing properties
2725     Annotation::store(node, document);
2726 
2727     // create [fileattachment] element
2728     QDomElement fileAttachmentElement = document.createElement(QStringLiteral("fileattachment"));
2729     node.appendChild(fileAttachmentElement);
2730 }
2731 
2732 Annotation::SubType FileAttachmentAnnotation::subType() const
2733 {
2734     return AFileAttachment;
2735 }
2736 
2737 QString FileAttachmentAnnotation::fileIconName() const
2738 {
2739     Q_D(const FileAttachmentAnnotation);
2740     return d->icon;
2741 }
2742 
2743 void FileAttachmentAnnotation::setFileIconName(const QString &iconName)
2744 {
2745     Q_D(FileAttachmentAnnotation);
2746     d->icon = iconName;
2747 }
2748 
2749 EmbeddedFile *FileAttachmentAnnotation::embeddedFile() const
2750 {
2751     Q_D(const FileAttachmentAnnotation);
2752     return d->embfile;
2753 }
2754 
2755 void FileAttachmentAnnotation::setEmbeddedFile(EmbeddedFile *ef)
2756 {
2757     Q_D(FileAttachmentAnnotation);
2758     d->embfile = ef;
2759 }
2760 
2761 /** SoundAnnotation [Annotation] */
2762 
2763 class Okular::SoundAnnotationPrivate : public Okular::AnnotationPrivate
2764 {
2765 public:
2766     SoundAnnotationPrivate()
2767         : AnnotationPrivate()
2768         , icon(QStringLiteral("Speaker"))
2769         , sound(nullptr)
2770     {
2771     }
2772     ~SoundAnnotationPrivate() override
2773     {
2774         delete sound;
2775     }
2776 
2777     void setAnnotationProperties(const QDomNode &node) override;
2778     AnnotationPrivate *getNewAnnotationPrivate() override;
2779 
2780     // data fields
2781     QString icon;
2782     Sound *sound;
2783 };
2784 
2785 void SoundAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
2786 {
2787     Okular::AnnotationPrivate::setAnnotationProperties(node);
2788 
2789     // loop through the whole children looking for a 'sound' element
2790     QDomNode subNode = node.firstChild();
2791     while (subNode.isElement()) {
2792         QDomElement e = subNode.toElement();
2793         subNode = subNode.nextSibling();
2794         if (e.tagName() != QLatin1String("sound")) {
2795             continue;
2796         }
2797 
2798         // loading complete
2799         break;
2800     }
2801 }
2802 
2803 AnnotationPrivate *SoundAnnotationPrivate::getNewAnnotationPrivate()
2804 {
2805     return new SoundAnnotationPrivate();
2806 }
2807 
2808 SoundAnnotation::SoundAnnotation()
2809     : Annotation(*new SoundAnnotationPrivate())
2810 {
2811 }
2812 
2813 SoundAnnotation::SoundAnnotation(const QDomNode &description)
2814     : Annotation(*new SoundAnnotationPrivate(), description)
2815 {
2816 }
2817 
2818 SoundAnnotation::~SoundAnnotation()
2819 {
2820 }
2821 
2822 void SoundAnnotation::store(QDomNode &node, QDomDocument &document) const
2823 {
2824     // recurse to parent objects storing properties
2825     Annotation::store(node, document);
2826 
2827     // create [sound] element
2828     QDomElement soundElement = document.createElement(QStringLiteral("sound"));
2829     node.appendChild(soundElement);
2830 }
2831 
2832 Annotation::SubType SoundAnnotation::subType() const
2833 {
2834     return ASound;
2835 }
2836 
2837 QString SoundAnnotation::soundIconName() const
2838 {
2839     Q_D(const SoundAnnotation);
2840     return d->icon;
2841 }
2842 
2843 void SoundAnnotation::setSoundIconName(const QString &iconName)
2844 {
2845     Q_D(SoundAnnotation);
2846     d->icon = iconName;
2847 }
2848 
2849 Sound *SoundAnnotation::sound() const
2850 {
2851     Q_D(const SoundAnnotation);
2852     return d->sound;
2853 }
2854 
2855 void SoundAnnotation::setSound(Sound *s)
2856 {
2857     Q_D(SoundAnnotation);
2858     d->sound = s;
2859 }
2860 
2861 /** MovieAnnotation [Annotation] */
2862 
2863 class Okular::MovieAnnotationPrivate : public Okular::AnnotationPrivate
2864 {
2865 public:
2866     MovieAnnotationPrivate()
2867         : AnnotationPrivate()
2868         , movie(nullptr)
2869     {
2870     }
2871     ~MovieAnnotationPrivate() override
2872     {
2873         delete movie;
2874     }
2875 
2876     void setAnnotationProperties(const QDomNode &node) override;
2877     AnnotationPrivate *getNewAnnotationPrivate() override;
2878 
2879     // data fields
2880     Movie *movie;
2881 };
2882 
2883 void MovieAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
2884 {
2885     Okular::AnnotationPrivate::setAnnotationProperties(node);
2886 
2887     // loop through the whole children looking for a 'movie' element
2888     QDomNode subNode = node.firstChild();
2889     while (subNode.isElement()) {
2890         QDomElement e = subNode.toElement();
2891         subNode = subNode.nextSibling();
2892         if (e.tagName() != QLatin1String("movie")) {
2893             continue;
2894         }
2895 
2896         // loading complete
2897         break;
2898     }
2899 }
2900 
2901 AnnotationPrivate *MovieAnnotationPrivate::getNewAnnotationPrivate()
2902 {
2903     return new MovieAnnotationPrivate();
2904 }
2905 
2906 MovieAnnotation::MovieAnnotation()
2907     : Annotation(*new MovieAnnotationPrivate())
2908 {
2909 }
2910 
2911 MovieAnnotation::MovieAnnotation(const QDomNode &description)
2912     : Annotation(*new MovieAnnotationPrivate(), description)
2913 {
2914 }
2915 
2916 MovieAnnotation::~MovieAnnotation()
2917 {
2918 }
2919 
2920 void MovieAnnotation::store(QDomNode &node, QDomDocument &document) const
2921 {
2922     // recurse to parent objects storing properties
2923     Annotation::store(node, document);
2924 
2925     // create [movie] element
2926     QDomElement movieElement = document.createElement(QStringLiteral("movie"));
2927     node.appendChild(movieElement);
2928 }
2929 
2930 Annotation::SubType MovieAnnotation::subType() const
2931 {
2932     return AMovie;
2933 }
2934 
2935 Movie *MovieAnnotation::movie() const
2936 {
2937     Q_D(const MovieAnnotation);
2938     return d->movie;
2939 }
2940 
2941 void MovieAnnotation::setMovie(Movie *movie)
2942 {
2943     Q_D(MovieAnnotation);
2944     d->movie = movie;
2945 }
2946 
2947 /** ScreenAnnotation [Annotation] */
2948 
2949 class Okular::ScreenAnnotationPrivate : public Okular::AnnotationPrivate
2950 {
2951 public:
2952     ScreenAnnotationPrivate();
2953     ~ScreenAnnotationPrivate() override;
2954 
2955     void setAnnotationProperties(const QDomNode &node) override;
2956     AnnotationPrivate *getNewAnnotationPrivate() override;
2957 
2958     Okular::Action *m_action;
2959     QMap<Okular::Annotation::AdditionalActionType, Okular::Action *> m_additionalActions;
2960 };
2961 
2962 ScreenAnnotationPrivate::ScreenAnnotationPrivate()
2963     : m_action(nullptr)
2964 {
2965 }
2966 
2967 ScreenAnnotationPrivate::~ScreenAnnotationPrivate()
2968 {
2969     delete m_action;
2970     qDeleteAll(m_additionalActions);
2971 }
2972 
2973 void ScreenAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
2974 {
2975     Okular::AnnotationPrivate::setAnnotationProperties(node);
2976 
2977     // loop through the whole children looking for a 'screen' element
2978     QDomNode subNode = node.firstChild();
2979     while (subNode.isElement()) {
2980         QDomElement e = subNode.toElement();
2981         subNode = subNode.nextSibling();
2982         if (e.tagName() != QLatin1String("screen")) {
2983             continue;
2984         }
2985 
2986         // loading complete
2987         break;
2988     }
2989 }
2990 
2991 AnnotationPrivate *ScreenAnnotationPrivate::getNewAnnotationPrivate()
2992 {
2993     return new ScreenAnnotationPrivate();
2994 }
2995 
2996 ScreenAnnotation::ScreenAnnotation()
2997     : Annotation(*new ScreenAnnotationPrivate())
2998 {
2999 }
3000 
3001 ScreenAnnotation::ScreenAnnotation(const QDomNode &description)
3002     : Annotation(*new ScreenAnnotationPrivate(), description)
3003 {
3004 }
3005 
3006 ScreenAnnotation::~ScreenAnnotation()
3007 {
3008 }
3009 
3010 void ScreenAnnotation::store(QDomNode &node, QDomDocument &document) const
3011 {
3012     // recurse to parent objects storing properties
3013     Annotation::store(node, document);
3014 
3015     // create [screen] element
3016     QDomElement movieElement = document.createElement(QStringLiteral("screen"));
3017     node.appendChild(movieElement);
3018 }
3019 
3020 Annotation::SubType ScreenAnnotation::subType() const
3021 {
3022     return AScreen;
3023 }
3024 
3025 void ScreenAnnotation::setAdditionalAction(AdditionalActionType type, Action *action)
3026 {
3027     Q_D(ScreenAnnotation);
3028     if (d->m_additionalActions.contains(type)) {
3029         delete d->m_additionalActions.value(type);
3030     }
3031 
3032     d->m_additionalActions.insert(type, action);
3033 }
3034 
3035 Action *ScreenAnnotation::additionalAction(AdditionalActionType type) const
3036 {
3037     Q_D(const ScreenAnnotation);
3038     if (!d->m_additionalActions.contains(type)) {
3039         return nullptr;
3040     } else {
3041         return d->m_additionalActions.value(type);
3042     }
3043 }
3044 
3045 void ScreenAnnotation::setAction(Action *action)
3046 {
3047     Q_D(ScreenAnnotation);
3048 
3049     delete d->m_action;
3050     d->m_action = action;
3051 }
3052 
3053 Action *ScreenAnnotation::action() const
3054 {
3055     Q_D(const ScreenAnnotation);
3056     return d->m_action;
3057 }
3058 
3059 /** WidgetAnnotation [Annotation] */
3060 
3061 class Okular::WidgetAnnotationPrivate : public Okular::AnnotationPrivate
3062 {
3063 public:
3064     ~WidgetAnnotationPrivate() override;
3065     void setAnnotationProperties(const QDomNode &node) override;
3066     AnnotationPrivate *getNewAnnotationPrivate() override;
3067 
3068     QMap<Okular::Annotation::AdditionalActionType, Okular::Action *> m_additionalActions;
3069 };
3070 
3071 WidgetAnnotationPrivate::~WidgetAnnotationPrivate()
3072 {
3073     qDeleteAll(m_additionalActions);
3074 }
3075 
3076 void WidgetAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
3077 {
3078     Okular::AnnotationPrivate::setAnnotationProperties(node);
3079 
3080     // loop through the whole children looking for a 'widget' element
3081     QDomNode subNode = node.firstChild();
3082     while (subNode.isElement()) {
3083         QDomElement e = subNode.toElement();
3084         subNode = subNode.nextSibling();
3085         if (e.tagName() != QLatin1String("widget")) {
3086             continue;
3087         }
3088 
3089         // loading complete
3090         break;
3091     }
3092 }
3093 
3094 AnnotationPrivate *WidgetAnnotationPrivate::getNewAnnotationPrivate()
3095 {
3096     return new WidgetAnnotationPrivate();
3097 }
3098 
3099 WidgetAnnotation::WidgetAnnotation()
3100     : Annotation(*new WidgetAnnotationPrivate())
3101 {
3102 }
3103 
3104 WidgetAnnotation::WidgetAnnotation(const QDomNode &description)
3105     : Annotation(*new WidgetAnnotationPrivate, description)
3106 {
3107 }
3108 
3109 WidgetAnnotation::~WidgetAnnotation()
3110 {
3111 }
3112 
3113 void WidgetAnnotation::store(QDomNode &node, QDomDocument &document) const
3114 {
3115     // recurse to parent objects storing properties
3116     Annotation::store(node, document);
3117 
3118     // create [widget] element
3119     QDomElement movieElement = document.createElement(QStringLiteral("widget"));
3120     node.appendChild(movieElement);
3121 }
3122 
3123 Annotation::SubType WidgetAnnotation::subType() const
3124 {
3125     return AWidget;
3126 }
3127 
3128 void WidgetAnnotation::setAdditionalAction(AdditionalActionType type, Action *action)
3129 {
3130     Q_D(WidgetAnnotation);
3131     if (d->m_additionalActions.contains(type)) {
3132         delete d->m_additionalActions.value(type);
3133     }
3134 
3135     d->m_additionalActions.insert(type, action);
3136 }
3137 
3138 Action *WidgetAnnotation::additionalAction(AdditionalActionType type) const
3139 {
3140     Q_D(const WidgetAnnotation);
3141     if (!d->m_additionalActions.contains(type)) {
3142         return nullptr;
3143     } else {
3144         return d->m_additionalActions.value(type);
3145     }
3146 }
3147 
3148 /** RichMediaAnnotation [Annotation] */
3149 
3150 class Okular::RichMediaAnnotationPrivate : public Okular::AnnotationPrivate
3151 {
3152 public:
3153     RichMediaAnnotationPrivate();
3154     ~RichMediaAnnotationPrivate() override;
3155     void setAnnotationProperties(const QDomNode &node) override;
3156     AnnotationPrivate *getNewAnnotationPrivate() override;
3157 
3158     // data fields
3159     Movie *movie;
3160     EmbeddedFile *embeddedFile;
3161 };
3162 
3163 RichMediaAnnotationPrivate::RichMediaAnnotationPrivate()
3164     : movie(nullptr)
3165     , embeddedFile(nullptr)
3166 {
3167 }
3168 
3169 RichMediaAnnotationPrivate::~RichMediaAnnotationPrivate()
3170 {
3171     delete movie;
3172     delete embeddedFile;
3173 }
3174 
3175 void RichMediaAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
3176 {
3177     Okular::AnnotationPrivate::setAnnotationProperties(node);
3178 
3179     // loop through the whole children looking for a 'richMedia' element
3180     QDomNode subNode = node.firstChild();
3181     while (subNode.isElement()) {
3182         QDomElement e = subNode.toElement();
3183         subNode = subNode.nextSibling();
3184         if (e.tagName() != QLatin1String("richMedia")) {
3185             continue;
3186         }
3187 
3188         // loading complete
3189         break;
3190     }
3191 }
3192 
3193 AnnotationPrivate *RichMediaAnnotationPrivate::getNewAnnotationPrivate()
3194 {
3195     return new RichMediaAnnotationPrivate();
3196 }
3197 
3198 RichMediaAnnotation::RichMediaAnnotation()
3199     : Annotation(*new RichMediaAnnotationPrivate())
3200 {
3201 }
3202 
3203 RichMediaAnnotation::RichMediaAnnotation(const QDomNode &description)
3204     : Annotation(*new RichMediaAnnotationPrivate, description)
3205 {
3206 }
3207 
3208 RichMediaAnnotation::~RichMediaAnnotation()
3209 {
3210 }
3211 
3212 void RichMediaAnnotation::store(QDomNode &node, QDomDocument &document) const
3213 {
3214     // recurse to parent objects storing properties
3215     Annotation::store(node, document);
3216 
3217     // create [richMedia] element
3218     QDomElement movieElement = document.createElement(QStringLiteral("richMedia"));
3219     node.appendChild(movieElement);
3220 }
3221 
3222 Annotation::SubType RichMediaAnnotation::subType() const
3223 {
3224     return ARichMedia;
3225 }
3226 
3227 void RichMediaAnnotation::setMovie(Movie *movie)
3228 {
3229     Q_D(RichMediaAnnotation);
3230 
3231     delete d->movie;
3232     d->movie = movie;
3233 }
3234 
3235 Movie *RichMediaAnnotation::movie() const
3236 {
3237     Q_D(const RichMediaAnnotation);
3238 
3239     return d->movie;
3240 }
3241 
3242 EmbeddedFile *RichMediaAnnotation::embeddedFile() const
3243 {
3244     Q_D(const RichMediaAnnotation);
3245 
3246     return d->embeddedFile;
3247 }
3248 
3249 void RichMediaAnnotation::setEmbeddedFile(EmbeddedFile *embeddedFile)
3250 {
3251     Q_D(RichMediaAnnotation);
3252 
3253     delete d->embeddedFile;
3254     d->embeddedFile = embeddedFile;
3255 }