File indexing completed on 2024-05-12 08:32:55

0001 /*
0002     SPDX-FileCopyrightText: 2004 Enrico Ros <eros.kde@email.it>
0003 
0004     Work sponsored by the LiMux project of the city of Munich:
0005     SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "page.h"
0011 #include "page_p.h"
0012 
0013 // qt/kde includes
0014 #include <QDomDocument>
0015 #include <QDomElement>
0016 #include <QHash>
0017 #include <QPixmap>
0018 #include <QSet>
0019 #include <QString>
0020 #include <QUuid>
0021 #include <QVariant>
0022 
0023 #include <QDebug>
0024 
0025 // local includes
0026 #include "action.h"
0027 #include "annotations.h"
0028 #include "annotations_p.h"
0029 #include "debug_p.h"
0030 #include "document.h"
0031 #include "document_p.h"
0032 #include "form.h"
0033 #include "form_p.h"
0034 #include "observer.h"
0035 #include "pagecontroller_p.h"
0036 #include "pagesize.h"
0037 #include "pagetransition.h"
0038 #include "rotationjob_p.h"
0039 #include "textpage_p.h"
0040 #include "tile.h"
0041 #include "tilesmanager_p.h"
0042 #include "utils_p.h"
0043 
0044 #include <limits>
0045 
0046 #ifdef PAGE_PROFILE
0047 #include <QTime>
0048 #endif
0049 
0050 using namespace Okular;
0051 
0052 static const double distanceConsideredEqual = 25; // 5px
0053 
0054 static void deleteObjectRects(QList<ObjectRect *> &rects, const QSet<ObjectRect::ObjectType> &which)
0055 {
0056     QList<ObjectRect *>::iterator it = rects.begin();
0057     for (; it != rects.end();) {
0058         if (which.contains((*it)->objectType())) {
0059             delete *it;
0060             it = rects.erase(it);
0061         } else {
0062             ++it;
0063         }
0064     }
0065 }
0066 
0067 PagePrivate::PagePrivate(Page *page, uint n, double w, double h, Rotation o)
0068     : m_page(page)
0069     , m_number(n)
0070     , m_orientation(o)
0071     , m_width(w)
0072     , m_height(h)
0073     , m_doc(nullptr)
0074     , m_boundingBox(0, 0, 1, 1)
0075     , m_rotation(Rotation0)
0076     , m_text(nullptr)
0077     , m_transition(nullptr)
0078     , m_textSelections(nullptr)
0079     , m_openingAction(nullptr)
0080     , m_closingAction(nullptr)
0081     , m_duration(-1)
0082     , m_isBoundingBoxKnown(false)
0083 {
0084     // avoid Division-By-Zero problems in the program
0085     if (m_width <= 0) {
0086         m_width = 1;
0087     }
0088 
0089     if (m_height <= 0) {
0090         m_height = 1;
0091     }
0092 }
0093 
0094 PagePrivate::~PagePrivate()
0095 {
0096     qDeleteAll(formfields);
0097     delete m_openingAction;
0098     delete m_closingAction;
0099     delete m_text;
0100     delete m_transition;
0101 }
0102 
0103 PagePrivate *PagePrivate::get(Page *page)
0104 {
0105     return page ? page->d : nullptr;
0106 }
0107 
0108 void PagePrivate::imageRotationDone(RotationJob *job)
0109 {
0110     TilesManager *tm = tilesManager(job->observer());
0111     if (tm) {
0112         QPixmap *pixmap = new QPixmap(QPixmap::fromImage(job->image()));
0113         tm->setPixmap(pixmap, job->rect(), job->isPartialUpdate());
0114         delete pixmap;
0115         return;
0116     }
0117 
0118     QMap<DocumentObserver *, PixmapObject>::iterator it = m_pixmaps.find(job->observer());
0119     if (it != m_pixmaps.end()) {
0120         PixmapObject &object = it.value();
0121         (*object.m_pixmap) = QPixmap::fromImage(job->image());
0122         object.m_rotation = job->rotation();
0123         object.m_isPartialPixmap = job->isPartialUpdate();
0124     } else {
0125         PixmapObject object;
0126         object.m_pixmap = new QPixmap(QPixmap::fromImage(job->image()));
0127         object.m_rotation = job->rotation();
0128         object.m_isPartialPixmap = job->isPartialUpdate();
0129 
0130         m_pixmaps.insert(job->observer(), object);
0131     }
0132 }
0133 
0134 QTransform PagePrivate::rotationMatrix() const
0135 {
0136     return Okular::buildRotationMatrix(m_rotation);
0137 }
0138 
0139 /** class Page **/
0140 
0141 Page::Page(uint pageNumber, double w, double h, Rotation o)
0142     : d(new PagePrivate(this, pageNumber, w, h, o))
0143 {
0144 }
0145 
0146 Page::~Page()
0147 {
0148     if (d) {
0149         deletePixmaps();
0150         deleteRects();
0151         d->deleteHighlights();
0152         deleteAnnotations();
0153         d->deleteTextSelections();
0154         deleteSourceReferences();
0155 
0156         delete d;
0157     }
0158 }
0159 
0160 int Page::number() const
0161 {
0162     return d->m_number;
0163 }
0164 
0165 Rotation Page::orientation() const
0166 {
0167     return d->m_orientation;
0168 }
0169 
0170 Rotation Page::rotation() const
0171 {
0172     return d->m_rotation;
0173 }
0174 
0175 Rotation Page::totalOrientation() const
0176 {
0177     return (Rotation)(((int)d->m_orientation + (int)d->m_rotation) % 4);
0178 }
0179 
0180 double Page::width() const
0181 {
0182     return d->m_width;
0183 }
0184 
0185 double Page::height() const
0186 {
0187     return d->m_height;
0188 }
0189 
0190 double Page::ratio() const
0191 {
0192     return d->m_height / d->m_width;
0193 }
0194 
0195 NormalizedRect Page::boundingBox() const
0196 {
0197     return d->m_boundingBox;
0198 }
0199 
0200 bool Page::isBoundingBoxKnown() const
0201 {
0202     return d->m_isBoundingBoxKnown;
0203 }
0204 
0205 void Page::setBoundingBox(const NormalizedRect &bbox)
0206 {
0207     if (d->m_isBoundingBoxKnown && d->m_boundingBox == bbox) {
0208         return;
0209     }
0210 
0211     // Allow tiny rounding errors (happens during rotation)
0212     static const double epsilon = 0.00001;
0213     Q_ASSERT(bbox.left >= -epsilon && bbox.top >= -epsilon && bbox.right <= 1 + epsilon && bbox.bottom <= 1 + epsilon);
0214 
0215     d->m_boundingBox = bbox & NormalizedRect(0., 0., 1., 1.);
0216     d->m_isBoundingBoxKnown = true;
0217 }
0218 
0219 bool Page::hasPixmap(DocumentObserver *observer, int width, int height, const NormalizedRect &rect) const
0220 {
0221     TilesManager *tm = d->tilesManager(observer);
0222     if (tm) {
0223         if (width != tm->width() || height != tm->height()) {
0224             return false;
0225         }
0226 
0227         return tm->hasPixmap(rect);
0228     }
0229 
0230     QMap<DocumentObserver *, PagePrivate::PixmapObject>::const_iterator it = d->m_pixmaps.constFind(observer);
0231     if (it == d->m_pixmaps.constEnd()) {
0232         return false;
0233     }
0234 
0235     if (width == -1 || height == -1) {
0236         return true;
0237     }
0238 
0239     if (it.value().m_isPartialPixmap) {
0240         return false;
0241     }
0242 
0243     const QPixmap *pixmap = it.value().m_pixmap;
0244 
0245     return (pixmap->width() == width && pixmap->height() == height);
0246 }
0247 
0248 void Page::setPageSize(DocumentObserver *observer, int width, int height)
0249 {
0250     TilesManager *tm = d->tilesManager(observer);
0251     if (tm) {
0252         tm->setSize(width, height);
0253     }
0254 }
0255 
0256 bool Page::hasTextPage() const
0257 {
0258     return d->m_text != nullptr;
0259 }
0260 
0261 RegularAreaRect *Page::wordAt(const NormalizedPoint &p, QString *word) const
0262 {
0263     if (d->m_text) {
0264         return d->m_text->wordAt(p, word);
0265     }
0266 
0267     return nullptr;
0268 }
0269 
0270 RegularAreaRect *Page::textArea(TextSelection *selection) const
0271 {
0272     if (d->m_text) {
0273         return d->m_text->textArea(selection);
0274     }
0275 
0276     return nullptr;
0277 }
0278 
0279 bool Page::hasObjectRect(double x, double y, double xScale, double yScale) const
0280 {
0281     if (m_rects.isEmpty()) {
0282         return false;
0283     }
0284 
0285     for (ObjectRect *rect : m_rects) {
0286         if (rect->distanceSqr(x, y, xScale, yScale) < distanceConsideredEqual) {
0287             return true;
0288         }
0289     }
0290 
0291     return false;
0292 }
0293 
0294 bool Page::hasHighlights(int s_id) const
0295 {
0296     // simple case: have no highlights
0297     if (m_highlights.isEmpty()) {
0298         return false;
0299     }
0300     // simple case: we have highlights and no id to match
0301     if (s_id == -1) {
0302         return true;
0303     }
0304     // iterate on the highlights list to find an entry by id
0305     for (HighlightAreaRect *highlight : m_highlights) {
0306         if (highlight->s_id == s_id) {
0307             return true;
0308         }
0309     }
0310     return false;
0311 }
0312 
0313 bool Page::hasTransition() const
0314 {
0315     return d->m_transition != nullptr;
0316 }
0317 
0318 bool Page::hasAnnotations() const
0319 {
0320     return !m_annotations.isEmpty();
0321 }
0322 
0323 RegularAreaRect *Page::findText(int id, const QString &text, SearchDirection direction, Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect) const
0324 {
0325     RegularAreaRect *rect = nullptr;
0326     if (text.isEmpty() || !d->m_text) {
0327         return rect;
0328     }
0329 
0330     rect = d->m_text->findText(id, text, direction, caseSensitivity, lastRect);
0331     return rect;
0332 }
0333 
0334 QString Page::text(const RegularAreaRect *area) const
0335 {
0336     return text(area, TextPage::AnyPixelTextAreaInclusionBehaviour);
0337 }
0338 
0339 QString Page::text(const RegularAreaRect *area, TextPage::TextAreaInclusionBehaviour b) const
0340 {
0341     QString ret;
0342 
0343     if (!d->m_text) {
0344         return ret;
0345     }
0346 
0347     if (area) {
0348         RegularAreaRect rotatedArea = *area;
0349         rotatedArea.transform(d->rotationMatrix().inverted());
0350 
0351         ret = d->m_text->text(&rotatedArea, b);
0352     } else {
0353         ret = d->m_text->text(nullptr, b);
0354     }
0355 
0356     return ret;
0357 }
0358 
0359 TextEntity::List Page::words(const RegularAreaRect *area, TextPage::TextAreaInclusionBehaviour b) const
0360 {
0361     TextEntity::List ret;
0362 
0363     if (!d->m_text) {
0364         return ret;
0365     }
0366 
0367     if (area) {
0368         RegularAreaRect rotatedArea = *area;
0369         rotatedArea.transform(d->rotationMatrix().inverted());
0370 
0371         ret = d->m_text->words(&rotatedArea, b);
0372     } else {
0373         ret = d->m_text->words(nullptr, b);
0374     }
0375 
0376     for (auto &retI : ret) {
0377         const TextEntity *orig = retI;
0378         retI = new TextEntity(orig->text(), new Okular::NormalizedRect(orig->transformedArea(d->rotationMatrix())));
0379         delete orig;
0380     }
0381 
0382     return ret;
0383 }
0384 
0385 void PagePrivate::rotateAt(Rotation orientation)
0386 {
0387     if (orientation == m_rotation) {
0388         return;
0389     }
0390 
0391     deleteTextSelections();
0392 
0393     if (((int)m_orientation + (int)m_rotation) % 2 != ((int)m_orientation + (int)orientation) % 2) {
0394         std::swap(m_width, m_height);
0395     }
0396 
0397     Rotation oldRotation = m_rotation;
0398     m_rotation = orientation;
0399 
0400     /**
0401      * Rotate the images of the page.
0402      */
0403     QMapIterator<DocumentObserver *, PagePrivate::PixmapObject> it(m_pixmaps);
0404     while (it.hasNext()) {
0405         it.next();
0406 
0407         const PagePrivate::PixmapObject &object = it.value();
0408 
0409         RotationJob *job = new RotationJob(object.m_pixmap->toImage(), object.m_rotation, m_rotation, it.key());
0410         job->setPage(this);
0411         m_doc->m_pageController->addRotationJob(job);
0412     }
0413 
0414     /**
0415      * Rotate tiles manager
0416      */
0417     QMapIterator<const DocumentObserver *, TilesManager *> i(m_tilesManagers);
0418     while (i.hasNext()) {
0419         i.next();
0420 
0421         TilesManager *tm = i.value();
0422         if (tm) {
0423             tm->setRotation(m_rotation);
0424         }
0425     }
0426 
0427     /**
0428      * Rotate the object rects on the page.
0429      */
0430     const QTransform matrix = rotationMatrix();
0431     for (ObjectRect *objRect : std::as_const(m_page->m_rects)) {
0432         objRect->transform(matrix);
0433     }
0434 
0435     const QTransform highlightRotationMatrix = Okular::buildRotationMatrix((Rotation)(((int)m_rotation - (int)oldRotation + 4) % 4));
0436     for (HighlightAreaRect *hlar : std::as_const(m_page->m_highlights)) {
0437         hlar->transform(highlightRotationMatrix);
0438     }
0439 }
0440 
0441 void PagePrivate::changeSize(const PageSize &size)
0442 {
0443     if (size.isNull() || (size.width() == m_width && size.height() == m_height)) {
0444         return;
0445     }
0446 
0447     m_page->deletePixmaps();
0448     //    deleteHighlights();
0449     //    deleteTextSelections();
0450 
0451     m_width = size.width();
0452     m_height = size.height();
0453     if (m_rotation % 2) {
0454         std::swap(m_width, m_height);
0455     }
0456 }
0457 
0458 const ObjectRect *Page::objectRect(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale) const
0459 {
0460     // Walk list in reverse order so that annotations in the foreground are preferred
0461     QListIterator<ObjectRect *> it(m_rects);
0462     it.toBack();
0463     while (it.hasPrevious()) {
0464         const ObjectRect *objrect = it.previous();
0465         if ((objrect->objectType() == type) && objrect->distanceSqr(x, y, xScale, yScale) < distanceConsideredEqual) {
0466             return objrect;
0467         }
0468     }
0469 
0470     return nullptr;
0471 }
0472 
0473 QList<const ObjectRect *> Page::objectRects(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale) const
0474 {
0475     QList<const ObjectRect *> result;
0476 
0477     QListIterator<ObjectRect *> it(m_rects);
0478     it.toBack();
0479     while (it.hasPrevious()) {
0480         const ObjectRect *objrect = it.previous();
0481         if ((objrect->objectType() == type) && objrect->distanceSqr(x, y, xScale, yScale) < distanceConsideredEqual) {
0482             result.append(objrect);
0483         }
0484     }
0485 
0486     return result;
0487 }
0488 
0489 const ObjectRect *Page::nearestObjectRect(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale, double *distance) const
0490 {
0491     ObjectRect *res = nullptr;
0492     double minDistance = std::numeric_limits<double>::max();
0493 
0494     for (ObjectRect *rect : m_rects) {
0495         if (rect->objectType() == type) {
0496             double d = rect->distanceSqr(x, y, xScale, yScale);
0497             if (d < minDistance) {
0498                 res = rect;
0499                 minDistance = d;
0500             }
0501         }
0502     }
0503 
0504     if (distance) {
0505         *distance = minDistance;
0506     }
0507     return res;
0508 }
0509 
0510 const PageTransition *Page::transition() const
0511 {
0512     return d->m_transition;
0513 }
0514 
0515 QList<Annotation *> Page::annotations() const
0516 {
0517     return m_annotations;
0518 }
0519 
0520 Annotation *Page::annotation(const QString &uniqueName) const
0521 {
0522     for (Annotation *a : m_annotations) {
0523         if (a->uniqueName() == uniqueName) {
0524             return a;
0525         }
0526     }
0527     return nullptr;
0528 }
0529 
0530 const Action *Page::pageAction(PageAction action) const
0531 {
0532     switch (action) {
0533     case Page::Opening:
0534         return d->m_openingAction;
0535         break;
0536     case Page::Closing:
0537         return d->m_closingAction;
0538         break;
0539     }
0540 
0541     return nullptr;
0542 }
0543 
0544 QList<FormField *> Page::formFields() const
0545 {
0546     return d->formfields;
0547 }
0548 
0549 void Page::setPixmap(DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect)
0550 {
0551     d->setPixmap(observer, pixmap, rect, false /*isPartialPixmap*/);
0552 }
0553 
0554 void PagePrivate::setPixmap(DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect, bool isPartialPixmap)
0555 {
0556     if (m_rotation == Rotation0) {
0557         TilesManager *tm = tilesManager(observer);
0558         if (tm) {
0559             tm->setPixmap(pixmap, rect, isPartialPixmap);
0560             delete pixmap;
0561             return;
0562         }
0563 
0564         QMap<DocumentObserver *, PagePrivate::PixmapObject>::iterator it = m_pixmaps.find(observer);
0565         if (it != m_pixmaps.end()) {
0566             delete it.value().m_pixmap;
0567         } else {
0568             it = m_pixmaps.insert(observer, PagePrivate::PixmapObject());
0569         }
0570         it.value().m_pixmap = pixmap;
0571         it.value().m_rotation = m_rotation;
0572         it.value().m_isPartialPixmap = isPartialPixmap;
0573     } else {
0574         // it can happen that we get a setPixmap while closing and thus the page controller is gone
0575         if (m_doc->m_pageController) {
0576             RotationJob *job = new RotationJob(pixmap->toImage(), Rotation0, m_rotation, observer);
0577             job->setPage(this);
0578             job->setRect(TilesManager::toRotatedRect(rect, m_rotation));
0579             job->setIsPartialUpdate(isPartialPixmap);
0580             m_doc->m_pageController->addRotationJob(job);
0581         }
0582 
0583         delete pixmap;
0584     }
0585 }
0586 
0587 void Page::setTextPage(TextPage *textPage)
0588 {
0589     delete d->m_text;
0590 
0591     d->m_text = textPage;
0592     if (d->m_text) {
0593         d->m_text->d->m_page = this;
0594         // Correct/optimize text order for search and text selection
0595         d->m_text->d->correctTextOrder();
0596     }
0597 }
0598 
0599 void Page::setObjectRects(const QList<ObjectRect *> &rects)
0600 {
0601     QSet<ObjectRect::ObjectType> which;
0602     which << ObjectRect::Action << ObjectRect::Image;
0603     deleteObjectRects(m_rects, which);
0604 
0605     /**
0606      * Rotate the object rects of the page.
0607      */
0608     const QTransform matrix = d->rotationMatrix();
0609 
0610     for (ObjectRect *objectRect : rects) {
0611         objectRect->transform(matrix);
0612     }
0613 
0614     m_rects << rects;
0615 }
0616 
0617 const QList<ObjectRect *> &Page::objectRects() const
0618 {
0619     return m_rects;
0620 }
0621 
0622 void PagePrivate::setHighlight(int s_id, RegularAreaRect *rect, const QColor &color)
0623 {
0624     HighlightAreaRect *hr = new HighlightAreaRect(rect);
0625     hr->s_id = s_id;
0626     hr->color = color;
0627 
0628     m_page->m_highlights.append(hr);
0629 }
0630 
0631 void PagePrivate::setTextSelections(RegularAreaRect *r, const QColor &color)
0632 {
0633     deleteTextSelections();
0634     if (r) {
0635         HighlightAreaRect *hr = new HighlightAreaRect(r);
0636         hr->s_id = -1;
0637         hr->color = color;
0638         m_textSelections = hr;
0639         delete r;
0640     }
0641 }
0642 
0643 void Page::setSourceReferences(const QList<SourceRefObjectRect *> &refRects)
0644 {
0645     deleteSourceReferences();
0646     for (SourceRefObjectRect *rect : refRects) {
0647         m_rects << rect;
0648     }
0649 }
0650 
0651 void Page::setDuration(double seconds)
0652 {
0653     d->m_duration = seconds;
0654 }
0655 
0656 double Page::duration() const
0657 {
0658     return d->m_duration;
0659 }
0660 
0661 void Page::setLabel(const QString &label)
0662 {
0663     d->m_label = label;
0664 }
0665 
0666 QString Page::label() const
0667 {
0668     return d->m_label;
0669 }
0670 
0671 const RegularAreaRect *Page::textSelection() const
0672 {
0673     return d->m_textSelections;
0674 }
0675 
0676 QColor Page::textSelectionColor() const
0677 {
0678     return d->m_textSelections ? d->m_textSelections->color : QColor();
0679 }
0680 
0681 void Page::addAnnotation(Annotation *annotation)
0682 {
0683     // Generate uniqueName: okular-{UUID}
0684     if (annotation->uniqueName().isEmpty()) {
0685         QString uniqueName = QStringLiteral("okular-") + QUuid::createUuid().toString();
0686         annotation->setUniqueName(uniqueName);
0687     }
0688     annotation->d_ptr->m_page = d;
0689     m_annotations.append(annotation);
0690 
0691     AnnotationObjectRect *rect = new AnnotationObjectRect(annotation);
0692 
0693     // Rotate the annotation on the page.
0694     const QTransform matrix = d->rotationMatrix();
0695     annotation->d_ptr->annotationTransform(matrix);
0696 
0697     m_rects.append(rect);
0698 }
0699 
0700 bool Page::removeAnnotation(Annotation *annotation)
0701 {
0702     if (!d->m_doc->m_parent->canRemovePageAnnotation(annotation)) {
0703         return false;
0704     }
0705 
0706     QList<Annotation *>::iterator aIt = m_annotations.begin();
0707     for (; aIt != m_annotations.end(); ++aIt) {
0708         if ((*aIt) && (*aIt)->uniqueName() == annotation->uniqueName()) {
0709             int rectfound = false;
0710             QList<ObjectRect *>::iterator it = m_rects.begin();
0711             for (; it != m_rects.end() && !rectfound; ++it) {
0712                 if (((*it)->objectType() == ObjectRect::OAnnotation) && ((*it)->object() == (*aIt))) {
0713                     delete *it;
0714                     it = m_rects.erase(it);
0715                     rectfound = true;
0716                 }
0717             }
0718             qCDebug(OkularCoreDebug) << "removed annotation:" << annotation->uniqueName();
0719             annotation->d_ptr->m_page = nullptr;
0720             m_annotations.erase(aIt);
0721             break;
0722         }
0723     }
0724 
0725     return true;
0726 }
0727 
0728 void Page::setTransition(PageTransition *transition)
0729 {
0730     delete d->m_transition;
0731     d->m_transition = transition;
0732 }
0733 
0734 void Page::setPageAction(PageAction action, Action *link)
0735 {
0736     switch (action) {
0737     case Page::Opening:
0738         delete d->m_openingAction;
0739         d->m_openingAction = link;
0740         break;
0741     case Page::Closing:
0742         delete d->m_closingAction;
0743         d->m_closingAction = link;
0744         break;
0745     }
0746 }
0747 
0748 void Page::setFormFields(const QList<FormField *> &fields)
0749 {
0750     qDeleteAll(d->formfields);
0751     d->formfields = fields;
0752     for (FormField *ff : std::as_const(d->formfields)) {
0753         ff->d_ptr->setDefault();
0754         ff->d_ptr->m_page = this;
0755     }
0756 }
0757 
0758 void Page::deletePixmap(DocumentObserver *observer)
0759 {
0760     TilesManager *tm = d->tilesManager(observer);
0761     if (tm) {
0762         delete tm;
0763         d->m_tilesManagers.remove(observer);
0764     } else {
0765         PagePrivate::PixmapObject object = d->m_pixmaps.take(observer);
0766         delete object.m_pixmap;
0767     }
0768 }
0769 
0770 void Page::deletePixmaps()
0771 {
0772     QMapIterator<DocumentObserver *, PagePrivate::PixmapObject> it(d->m_pixmaps);
0773     while (it.hasNext()) {
0774         it.next();
0775         delete it.value().m_pixmap;
0776     }
0777 
0778     d->m_pixmaps.clear();
0779 
0780     qDeleteAll(d->m_tilesManagers);
0781     d->m_tilesManagers.clear();
0782 }
0783 
0784 void Page::deleteRects()
0785 {
0786     // delete ObjectRects of type Link and Image
0787     QSet<ObjectRect::ObjectType> which;
0788     which << ObjectRect::Action << ObjectRect::Image;
0789     deleteObjectRects(m_rects, which);
0790 }
0791 
0792 void PagePrivate::deleteHighlights(int s_id)
0793 {
0794     // delete highlights by ID
0795     QList<HighlightAreaRect *>::iterator it = m_page->m_highlights.begin();
0796     while (it != m_page->m_highlights.end()) {
0797         HighlightAreaRect *highlight = *it;
0798         if (s_id == -1 || highlight->s_id == s_id) {
0799             it = m_page->m_highlights.erase(it);
0800             delete highlight;
0801         } else {
0802             ++it;
0803         }
0804     }
0805 }
0806 
0807 void PagePrivate::deleteTextSelections()
0808 {
0809     delete m_textSelections;
0810     m_textSelections = nullptr;
0811 }
0812 
0813 void Page::deleteSourceReferences()
0814 {
0815     deleteObjectRects(m_rects, QSet<ObjectRect::ObjectType>() << ObjectRect::SourceRef);
0816 }
0817 
0818 void Page::deleteAnnotations()
0819 {
0820     // delete ObjectRects of type Annotation
0821     deleteObjectRects(m_rects, QSet<ObjectRect::ObjectType>() << ObjectRect::OAnnotation);
0822     // delete all stored annotations
0823     qDeleteAll(m_annotations);
0824     m_annotations.clear();
0825 }
0826 
0827 bool PagePrivate::restoreLocalContents(const QDomNode &pageNode)
0828 {
0829     bool loadedAnything = false; // set if something actually gets loaded
0830 
0831     // iterate over all children (annotationList, ...)
0832     QDomNode childNode = pageNode.firstChild();
0833     while (childNode.isElement()) {
0834         QDomElement childElement = childNode.toElement();
0835         childNode = childNode.nextSibling();
0836 
0837         // parse annotationList child element
0838         if (childElement.tagName() == QLatin1String("annotationList")) {
0839 #ifdef PAGE_PROFILE
0840             QTime time;
0841             time.start();
0842 #endif
0843             // Clone annotationList as root node in restoredLocalAnnotationList
0844             const QDomNode clonedNode = restoredLocalAnnotationList.importNode(childElement, true);
0845             restoredLocalAnnotationList.appendChild(clonedNode);
0846 
0847             // iterate over all annotations
0848             QDomNode annotationNode = childElement.firstChild();
0849             while (annotationNode.isElement()) {
0850                 // get annotation element and advance to next annot
0851                 QDomElement annotElement = annotationNode.toElement();
0852                 annotationNode = annotationNode.nextSibling();
0853 
0854                 // get annotation from the dom element
0855                 Annotation *annotation = AnnotationUtils::createAnnotation(annotElement);
0856 
0857                 // append annotation to the list or show warning
0858                 if (annotation) {
0859                     m_doc->performAddPageAnnotation(m_number, annotation);
0860                     qCDebug(OkularCoreDebug) << "restored annot:" << annotation->uniqueName();
0861                     loadedAnything = true;
0862                 } else {
0863                     qCWarning(OkularCoreDebug).nospace() << "page (" << m_number << "): can't restore an annotation from XML.";
0864                 }
0865             }
0866 #ifdef PAGE_PROFILE
0867             qCDebug(OkularCoreDebug).nospace() << "annots: XML Load time: " << time.elapsed() << "ms";
0868 #endif
0869         }
0870         // parse formList child element
0871         else if (childElement.tagName() == QLatin1String("forms")) {
0872             // Clone forms as root node in restoredFormFieldList
0873             const QDomNode clonedNode = restoredFormFieldList.importNode(childElement, true);
0874             restoredFormFieldList.appendChild(clonedNode);
0875 
0876             if (formfields.isEmpty()) {
0877                 continue;
0878             }
0879 
0880             QHash<int, FormField *> hashedforms;
0881             for (FormField *ff : std::as_const(formfields)) {
0882                 hashedforms[ff->id()] = ff;
0883             }
0884 
0885             // iterate over all forms
0886             QDomNode formsNode = childElement.firstChild();
0887             while (formsNode.isElement()) {
0888                 // get annotation element and advance to next annot
0889                 QDomElement formElement = formsNode.toElement();
0890                 formsNode = formsNode.nextSibling();
0891 
0892                 if (formElement.tagName() != QLatin1String("form")) {
0893                     continue;
0894                 }
0895 
0896                 bool ok = true;
0897                 int index = formElement.attribute(QStringLiteral("id")).toInt(&ok);
0898                 if (!ok) {
0899                     continue;
0900                 }
0901 
0902                 QHash<int, FormField *>::const_iterator wantedIt = hashedforms.constFind(index);
0903                 if (wantedIt == hashedforms.constEnd()) {
0904                     continue;
0905                 }
0906 
0907                 QString value = formElement.attribute(QStringLiteral("value"));
0908                 (*wantedIt)->d_ptr->setValue(value);
0909                 loadedAnything = true;
0910             }
0911         }
0912     }
0913 
0914     return loadedAnything;
0915 }
0916 
0917 void PagePrivate::saveLocalContents(QDomNode &parentNode, QDomDocument &document, PageItems what) const
0918 {
0919     // create the page node and set the 'number' attribute
0920     QDomElement pageElement = document.createElement(QStringLiteral("page"));
0921     pageElement.setAttribute(QStringLiteral("number"), m_number);
0922 
0923     // add annotations info if has got any
0924     if ((what & AnnotationPageItems) && (what & OriginalAnnotationPageItems)) {
0925         const QDomElement savedDocRoot = restoredLocalAnnotationList.documentElement();
0926         if (!savedDocRoot.isNull()) {
0927             // Import and append node in target document
0928             const QDomNode importedNode = document.importNode(savedDocRoot, true);
0929             pageElement.appendChild(importedNode);
0930         }
0931     } else if ((what & AnnotationPageItems) && !m_page->m_annotations.isEmpty()) {
0932         // create the annotationList
0933         QDomElement annotListElement = document.createElement(QStringLiteral("annotationList"));
0934 
0935         // add every annotation to the annotationList
0936         for (const Annotation *a : std::as_const(m_page->m_annotations)) {
0937             // only save okular annotations (not the embedded in file ones)
0938             if (!(a->flags() & Annotation::External)) {
0939                 // append an filled-up element called 'annotation' to the list
0940                 QDomElement annElement = document.createElement(QStringLiteral("annotation"));
0941                 AnnotationUtils::storeAnnotation(a, annElement, document);
0942                 annotListElement.appendChild(annElement);
0943                 qCDebug(OkularCoreDebug) << "save annotation:" << a->uniqueName();
0944             }
0945         }
0946 
0947         // append the annotationList element if annotations have been set
0948         if (annotListElement.hasChildNodes()) {
0949             pageElement.appendChild(annotListElement);
0950         }
0951     }
0952 
0953     // add forms info if has got any
0954     if ((what & FormFieldPageItems) && (what & OriginalFormFieldPageItems)) {
0955         const QDomElement savedDocRoot = restoredFormFieldList.documentElement();
0956         if (!savedDocRoot.isNull()) {
0957             // Import and append node in target document
0958             const QDomNode importedNode = document.importNode(savedDocRoot, true);
0959             pageElement.appendChild(importedNode);
0960         }
0961     } else if ((what & FormFieldPageItems) && !formfields.isEmpty()) {
0962         // create the formList
0963         QDomElement formListElement = document.createElement(QStringLiteral("forms"));
0964 
0965         // add every form data to the formList
0966         for (const FormField *f : formfields) {
0967             QString newvalue = f->d_ptr->value();
0968             if (f->d_ptr->m_default == newvalue) {
0969                 continue;
0970             }
0971 
0972             // append an filled-up element called 'annotation' to the list
0973             QDomElement formElement = document.createElement(QStringLiteral("form"));
0974             formElement.setAttribute(QStringLiteral("id"), f->id());
0975             formElement.setAttribute(QStringLiteral("value"), newvalue);
0976             formListElement.appendChild(formElement);
0977         }
0978 
0979         // append the annotationList element if annotations have been set
0980         if (formListElement.hasChildNodes()) {
0981             pageElement.appendChild(formListElement);
0982         }
0983     }
0984 
0985     // append the page element only if has children
0986     if (pageElement.hasChildNodes()) {
0987         parentNode.appendChild(pageElement);
0988     }
0989 }
0990 
0991 const QPixmap *Page::_o_nearestPixmap(DocumentObserver *observer, int w, int h) const
0992 {
0993     Q_UNUSED(h)
0994 
0995     const QPixmap *pixmap = nullptr;
0996 
0997     // if a pixmap is present for given id, use it
0998     QMap<DocumentObserver *, PagePrivate::PixmapObject>::const_iterator itPixmap = d->m_pixmaps.constFind(observer);
0999     if (itPixmap != d->m_pixmaps.constEnd()) {
1000         pixmap = itPixmap.value().m_pixmap;
1001     } else if (!d->m_pixmaps.isEmpty()) {
1002         // else find the closest match using pixmaps of other IDs (great optim!)
1003         int minDistance = -1;
1004         QMap<DocumentObserver *, PagePrivate::PixmapObject>::const_iterator it = d->m_pixmaps.constBegin(), end = d->m_pixmaps.constEnd();
1005         for (; it != end; ++it) {
1006             int pixWidth = (*it).m_pixmap->width(), distance = pixWidth > w ? pixWidth - w : w - pixWidth;
1007             if (minDistance == -1 || distance < minDistance) {
1008                 pixmap = (*it).m_pixmap;
1009                 minDistance = distance;
1010             }
1011         }
1012     }
1013 
1014     return pixmap;
1015 }
1016 
1017 bool Page::hasTilesManager(const DocumentObserver *observer) const
1018 {
1019     return d->tilesManager(observer) != nullptr;
1020 }
1021 
1022 QList<Tile> Page::tilesAt(const DocumentObserver *observer, const NormalizedRect &rect) const
1023 {
1024     TilesManager *tm = d->m_tilesManagers.value(observer);
1025     if (tm) {
1026         return tm->tilesAt(rect, TilesManager::PixmapTile);
1027     } else {
1028         return QList<Tile>();
1029     }
1030 }
1031 
1032 TilesManager *PagePrivate::tilesManager(const DocumentObserver *observer) const
1033 {
1034     return m_tilesManagers.value(observer);
1035 }
1036 
1037 void PagePrivate::setTilesManager(const DocumentObserver *observer, TilesManager *tm)
1038 {
1039     TilesManager *old = m_tilesManagers.value(observer);
1040     delete old;
1041 
1042     m_tilesManagers.insert(observer, tm);
1043 }
1044 
1045 void PagePrivate::adoptGeneratedContents(PagePrivate *oldPage)
1046 {
1047     rotateAt(oldPage->m_rotation);
1048 
1049     m_pixmaps = oldPage->m_pixmaps;
1050     oldPage->m_pixmaps.clear();
1051 
1052     m_tilesManagers = oldPage->m_tilesManagers;
1053     oldPage->m_tilesManagers.clear();
1054 
1055     m_boundingBox = oldPage->m_boundingBox;
1056     m_isBoundingBoxKnown = oldPage->m_isBoundingBoxKnown;
1057     m_text = oldPage->m_text;
1058     oldPage->m_text = nullptr;
1059 
1060     m_textSelections = oldPage->m_textSelections;
1061     oldPage->m_textSelections = nullptr;
1062 
1063     restoredLocalAnnotationList = oldPage->restoredLocalAnnotationList;
1064     restoredFormFieldList = oldPage->restoredFormFieldList;
1065 }
1066 
1067 FormField *PagePrivate::findEquivalentForm(const Page *p, FormField *oldField)
1068 {
1069     // given how id is not very good of id (at least for pdf) we do a few passes
1070     // same rect, type and id
1071     for (FormField *f : std::as_const(p->d->formfields)) {
1072         if (f->rect() == oldField->rect() && f->type() == oldField->type() && f->id() == oldField->id()) {
1073             return f;
1074         }
1075     }
1076     // same rect and type
1077     for (FormField *f : std::as_const(p->d->formfields)) {
1078         if (f->rect() == oldField->rect() && f->type() == oldField->type()) {
1079             return f;
1080         }
1081     }
1082     // fuzzy rect, same type and id
1083     for (FormField *f : std::as_const(p->d->formfields)) {
1084         if (f->type() == oldField->type() && f->id() == oldField->id() && qFuzzyCompare(f->rect().left, oldField->rect().left) && qFuzzyCompare(f->rect().top, oldField->rect().top) &&
1085             qFuzzyCompare(f->rect().right, oldField->rect().right) && qFuzzyCompare(f->rect().bottom, oldField->rect().bottom)) {
1086             return f;
1087         }
1088     }
1089     // fuzzy rect and same type
1090     for (FormField *f : std::as_const(p->d->formfields)) {
1091         if (f->type() == oldField->type() && qFuzzyCompare(f->rect().left, oldField->rect().left) && qFuzzyCompare(f->rect().top, oldField->rect().top) && qFuzzyCompare(f->rect().right, oldField->rect().right) &&
1092             qFuzzyCompare(f->rect().bottom, oldField->rect().bottom)) {
1093             return f;
1094         }
1095     }
1096     return nullptr;
1097 }