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

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         retI = TextEntity(retI.text(), Okular::NormalizedRect(retI.transformedArea(d->rotationMatrix())));
0378     }
0379 
0380     return ret;
0381 }
0382 
0383 void PagePrivate::rotateAt(Rotation orientation)
0384 {
0385     if (orientation == m_rotation) {
0386         return;
0387     }
0388 
0389     deleteTextSelections();
0390 
0391     if (((int)m_orientation + (int)m_rotation) % 2 != ((int)m_orientation + (int)orientation) % 2) {
0392         std::swap(m_width, m_height);
0393     }
0394 
0395     Rotation oldRotation = m_rotation;
0396     m_rotation = orientation;
0397 
0398     /**
0399      * Rotate the images of the page.
0400      */
0401     QMapIterator<DocumentObserver *, PagePrivate::PixmapObject> it(m_pixmaps);
0402     while (it.hasNext()) {
0403         it.next();
0404 
0405         const PagePrivate::PixmapObject &object = it.value();
0406 
0407         RotationJob *job = new RotationJob(object.m_pixmap->toImage(), object.m_rotation, m_rotation, it.key());
0408         job->setPage(this);
0409         m_doc->m_pageController->addRotationJob(job);
0410     }
0411 
0412     /**
0413      * Rotate tiles manager
0414      */
0415     QMapIterator<const DocumentObserver *, TilesManager *> i(m_tilesManagers);
0416     while (i.hasNext()) {
0417         i.next();
0418 
0419         TilesManager *tm = i.value();
0420         if (tm) {
0421             tm->setRotation(m_rotation);
0422         }
0423     }
0424 
0425     /**
0426      * Rotate the object rects on the page.
0427      */
0428     const QTransform matrix = rotationMatrix();
0429     for (ObjectRect *objRect : std::as_const(m_page->m_rects)) {
0430         objRect->transform(matrix);
0431     }
0432 
0433     const QTransform highlightRotationMatrix = Okular::buildRotationMatrix((Rotation)(((int)m_rotation - (int)oldRotation + 4) % 4));
0434     for (HighlightAreaRect *hlar : std::as_const(m_page->m_highlights)) {
0435         hlar->transform(highlightRotationMatrix);
0436     }
0437 }
0438 
0439 void PagePrivate::changeSize(const PageSize &size)
0440 {
0441     if (size.isNull() || (size.width() == m_width && size.height() == m_height)) {
0442         return;
0443     }
0444 
0445     m_page->deletePixmaps();
0446     //    deleteHighlights();
0447     //    deleteTextSelections();
0448 
0449     m_width = size.width();
0450     m_height = size.height();
0451     if (m_rotation % 2) {
0452         std::swap(m_width, m_height);
0453     }
0454 }
0455 
0456 const ObjectRect *Page::objectRect(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale) const
0457 {
0458     // Walk list in reverse order so that annotations in the foreground are preferred
0459     QListIterator<ObjectRect *> it(m_rects);
0460     it.toBack();
0461     while (it.hasPrevious()) {
0462         const ObjectRect *objrect = it.previous();
0463         if ((objrect->objectType() == type) && objrect->distanceSqr(x, y, xScale, yScale) < distanceConsideredEqual) {
0464             return objrect;
0465         }
0466     }
0467 
0468     return nullptr;
0469 }
0470 
0471 QList<const ObjectRect *> Page::objectRects(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale) const
0472 {
0473     QList<const ObjectRect *> result;
0474 
0475     QListIterator<ObjectRect *> it(m_rects);
0476     it.toBack();
0477     while (it.hasPrevious()) {
0478         const ObjectRect *objrect = it.previous();
0479         if ((objrect->objectType() == type) && objrect->distanceSqr(x, y, xScale, yScale) < distanceConsideredEqual) {
0480             result.append(objrect);
0481         }
0482     }
0483 
0484     return result;
0485 }
0486 
0487 const ObjectRect *Page::nearestObjectRect(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale, double *distance) const
0488 {
0489     ObjectRect *res = nullptr;
0490     double minDistance = std::numeric_limits<double>::max();
0491 
0492     for (ObjectRect *rect : m_rects) {
0493         if (rect->objectType() == type) {
0494             double d = rect->distanceSqr(x, y, xScale, yScale);
0495             if (d < minDistance) {
0496                 res = rect;
0497                 minDistance = d;
0498             }
0499         }
0500     }
0501 
0502     if (distance) {
0503         *distance = minDistance;
0504     }
0505     return res;
0506 }
0507 
0508 const PageTransition *Page::transition() const
0509 {
0510     return d->m_transition;
0511 }
0512 
0513 QList<Annotation *> Page::annotations() const
0514 {
0515     return m_annotations;
0516 }
0517 
0518 Annotation *Page::annotation(const QString &uniqueName) const
0519 {
0520     for (Annotation *a : m_annotations) {
0521         if (a->uniqueName() == uniqueName) {
0522             return a;
0523         }
0524     }
0525     return nullptr;
0526 }
0527 
0528 const Action *Page::pageAction(PageAction action) const
0529 {
0530     switch (action) {
0531     case Page::Opening:
0532         return d->m_openingAction;
0533         break;
0534     case Page::Closing:
0535         return d->m_closingAction;
0536         break;
0537     }
0538 
0539     return nullptr;
0540 }
0541 
0542 QList<FormField *> Page::formFields() const
0543 {
0544     return d->formfields;
0545 }
0546 
0547 void Page::setPixmap(DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect)
0548 {
0549     d->setPixmap(observer, pixmap, rect, false /*isPartialPixmap*/);
0550 }
0551 
0552 void PagePrivate::setPixmap(DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect, bool isPartialPixmap)
0553 {
0554     if (m_rotation == Rotation0) {
0555         TilesManager *tm = tilesManager(observer);
0556         if (tm) {
0557             tm->setPixmap(pixmap, rect, isPartialPixmap);
0558             delete pixmap;
0559             return;
0560         }
0561 
0562         QMap<DocumentObserver *, PagePrivate::PixmapObject>::iterator it = m_pixmaps.find(observer);
0563         if (it != m_pixmaps.end()) {
0564             delete it.value().m_pixmap;
0565         } else {
0566             it = m_pixmaps.insert(observer, PagePrivate::PixmapObject());
0567         }
0568         it.value().m_pixmap = pixmap;
0569         it.value().m_rotation = m_rotation;
0570         it.value().m_isPartialPixmap = isPartialPixmap;
0571     } else {
0572         // it can happen that we get a setPixmap while closing and thus the page controller is gone
0573         if (m_doc->m_pageController) {
0574             RotationJob *job = new RotationJob(pixmap->toImage(), Rotation0, m_rotation, observer);
0575             job->setPage(this);
0576             job->setRect(TilesManager::toRotatedRect(rect, m_rotation));
0577             job->setIsPartialUpdate(isPartialPixmap);
0578             m_doc->m_pageController->addRotationJob(job);
0579         }
0580 
0581         delete pixmap;
0582     }
0583 }
0584 
0585 void Page::setTextPage(TextPage *textPage)
0586 {
0587     delete d->m_text;
0588 
0589     d->m_text = textPage;
0590     if (d->m_text) {
0591         d->m_text->d->m_page = this;
0592         // Correct/optimize text order for search and text selection
0593         d->m_text->d->correctTextOrder();
0594     }
0595 }
0596 
0597 void Page::setObjectRects(const QList<ObjectRect *> &rects)
0598 {
0599     QSet<ObjectRect::ObjectType> which;
0600     which << ObjectRect::Action << ObjectRect::Image;
0601     deleteObjectRects(m_rects, which);
0602 
0603     /**
0604      * Rotate the object rects of the page.
0605      */
0606     const QTransform matrix = d->rotationMatrix();
0607 
0608     for (ObjectRect *objectRect : rects) {
0609         objectRect->transform(matrix);
0610     }
0611 
0612     m_rects << rects;
0613 }
0614 
0615 const QList<ObjectRect *> &Page::objectRects() const
0616 {
0617     return m_rects;
0618 }
0619 
0620 void PagePrivate::setHighlight(int s_id, RegularAreaRect *rect, const QColor &color)
0621 {
0622     HighlightAreaRect *hr = new HighlightAreaRect(rect);
0623     hr->s_id = s_id;
0624     hr->color = color;
0625 
0626     m_page->m_highlights.append(hr);
0627 }
0628 
0629 void PagePrivate::setTextSelections(RegularAreaRect *r, const QColor &color)
0630 {
0631     deleteTextSelections();
0632     if (r) {
0633         HighlightAreaRect *hr = new HighlightAreaRect(r);
0634         hr->s_id = -1;
0635         hr->color = color;
0636         m_textSelections = hr;
0637         delete r;
0638     }
0639 }
0640 
0641 void Page::setSourceReferences(const QList<SourceRefObjectRect *> &refRects)
0642 {
0643     deleteSourceReferences();
0644     for (SourceRefObjectRect *rect : refRects) {
0645         m_rects << rect;
0646     }
0647 }
0648 
0649 void Page::setDuration(double seconds)
0650 {
0651     d->m_duration = seconds;
0652 }
0653 
0654 double Page::duration() const
0655 {
0656     return d->m_duration;
0657 }
0658 
0659 void Page::setLabel(const QString &label)
0660 {
0661     d->m_label = label;
0662 }
0663 
0664 QString Page::label() const
0665 {
0666     return d->m_label;
0667 }
0668 
0669 const RegularAreaRect *Page::textSelection() const
0670 {
0671     return d->m_textSelections;
0672 }
0673 
0674 QColor Page::textSelectionColor() const
0675 {
0676     return d->m_textSelections ? d->m_textSelections->color : QColor();
0677 }
0678 
0679 void Page::addAnnotation(Annotation *annotation)
0680 {
0681     // Generate uniqueName: okular-{UUID}
0682     if (annotation->uniqueName().isEmpty()) {
0683         QString uniqueName = QStringLiteral("okular-") + QUuid::createUuid().toString();
0684         annotation->setUniqueName(uniqueName);
0685     }
0686     annotation->d_ptr->m_page = d;
0687     m_annotations.append(annotation);
0688 
0689     AnnotationObjectRect *rect = new AnnotationObjectRect(annotation);
0690 
0691     // Rotate the annotation on the page.
0692     const QTransform matrix = d->rotationMatrix();
0693     annotation->d_ptr->annotationTransform(matrix);
0694 
0695     m_rects.append(rect);
0696 }
0697 
0698 bool Page::removeAnnotation(Annotation *annotation)
0699 {
0700     if (!d->m_doc->m_parent->canRemovePageAnnotation(annotation)) {
0701         return false;
0702     }
0703 
0704     QList<Annotation *>::iterator aIt = m_annotations.begin();
0705     for (; aIt != m_annotations.end(); ++aIt) {
0706         if ((*aIt) && (*aIt)->uniqueName() == annotation->uniqueName()) {
0707             int rectfound = false;
0708             QList<ObjectRect *>::iterator it = m_rects.begin();
0709             for (; it != m_rects.end() && !rectfound; ++it) {
0710                 if (((*it)->objectType() == ObjectRect::OAnnotation) && ((*it)->object() == (*aIt))) {
0711                     delete *it;
0712                     it = m_rects.erase(it);
0713                     rectfound = true;
0714                 }
0715             }
0716             qCDebug(OkularCoreDebug) << "removed annotation:" << annotation->uniqueName();
0717             annotation->d_ptr->m_page = nullptr;
0718             m_annotations.erase(aIt);
0719             break;
0720         }
0721     }
0722 
0723     return true;
0724 }
0725 
0726 void Page::setTransition(PageTransition *transition)
0727 {
0728     delete d->m_transition;
0729     d->m_transition = transition;
0730 }
0731 
0732 void Page::setPageAction(PageAction action, Action *link)
0733 {
0734     switch (action) {
0735     case Page::Opening:
0736         delete d->m_openingAction;
0737         d->m_openingAction = link;
0738         break;
0739     case Page::Closing:
0740         delete d->m_closingAction;
0741         d->m_closingAction = link;
0742         break;
0743     }
0744 }
0745 
0746 void Page::setFormFields(const QList<FormField *> &fields)
0747 {
0748     qDeleteAll(d->formfields);
0749     d->formfields = fields;
0750     for (FormField *ff : std::as_const(d->formfields)) {
0751         ff->d_ptr->setDefault();
0752         ff->d_ptr->m_page = this;
0753     }
0754 }
0755 
0756 void Page::deletePixmap(DocumentObserver *observer)
0757 {
0758     TilesManager *tm = d->tilesManager(observer);
0759     if (tm) {
0760         delete tm;
0761         d->m_tilesManagers.remove(observer);
0762     } else {
0763         PagePrivate::PixmapObject object = d->m_pixmaps.take(observer);
0764         delete object.m_pixmap;
0765     }
0766 }
0767 
0768 void Page::deletePixmaps()
0769 {
0770     QMapIterator<DocumentObserver *, PagePrivate::PixmapObject> it(d->m_pixmaps);
0771     while (it.hasNext()) {
0772         it.next();
0773         delete it.value().m_pixmap;
0774     }
0775 
0776     d->m_pixmaps.clear();
0777 
0778     qDeleteAll(d->m_tilesManagers);
0779     d->m_tilesManagers.clear();
0780 }
0781 
0782 void Page::deleteRects()
0783 {
0784     // delete ObjectRects of type Link and Image
0785     QSet<ObjectRect::ObjectType> which;
0786     which << ObjectRect::Action << ObjectRect::Image;
0787     deleteObjectRects(m_rects, which);
0788 }
0789 
0790 void PagePrivate::deleteHighlights(int s_id)
0791 {
0792     // delete highlights by ID
0793     QList<HighlightAreaRect *>::iterator it = m_page->m_highlights.begin();
0794     while (it != m_page->m_highlights.end()) {
0795         HighlightAreaRect *highlight = *it;
0796         if (s_id == -1 || highlight->s_id == s_id) {
0797             it = m_page->m_highlights.erase(it);
0798             delete highlight;
0799         } else {
0800             ++it;
0801         }
0802     }
0803 }
0804 
0805 void PagePrivate::deleteTextSelections()
0806 {
0807     delete m_textSelections;
0808     m_textSelections = nullptr;
0809 }
0810 
0811 void Page::deleteSourceReferences()
0812 {
0813     deleteObjectRects(m_rects, QSet<ObjectRect::ObjectType>() << ObjectRect::SourceRef);
0814 }
0815 
0816 void Page::deleteAnnotations()
0817 {
0818     // delete ObjectRects of type Annotation
0819     deleteObjectRects(m_rects, QSet<ObjectRect::ObjectType>() << ObjectRect::OAnnotation);
0820     // delete all stored annotations
0821     qDeleteAll(m_annotations);
0822     m_annotations.clear();
0823 }
0824 
0825 bool PagePrivate::restoreLocalContents(const QDomNode &pageNode)
0826 {
0827     bool loadedAnything = false; // set if something actually gets loaded
0828 
0829     // iterate over all children (annotationList, ...)
0830     QDomNode childNode = pageNode.firstChild();
0831     while (childNode.isElement()) {
0832         QDomElement childElement = childNode.toElement();
0833         childNode = childNode.nextSibling();
0834 
0835         // parse annotationList child element
0836         if (childElement.tagName() == QLatin1String("annotationList")) {
0837 #ifdef PAGE_PROFILE
0838             QTime time;
0839             time.start();
0840 #endif
0841             // Clone annotationList as root node in restoredLocalAnnotationList
0842             const QDomNode clonedNode = restoredLocalAnnotationList.importNode(childElement, true);
0843             restoredLocalAnnotationList.appendChild(clonedNode);
0844 
0845             // iterate over all annotations
0846             QDomNode annotationNode = childElement.firstChild();
0847             while (annotationNode.isElement()) {
0848                 // get annotation element and advance to next annot
0849                 QDomElement annotElement = annotationNode.toElement();
0850                 annotationNode = annotationNode.nextSibling();
0851 
0852                 // get annotation from the dom element
0853                 Annotation *annotation = AnnotationUtils::createAnnotation(annotElement);
0854 
0855                 // append annotation to the list or show warning
0856                 if (annotation) {
0857                     m_doc->performAddPageAnnotation(m_number, annotation);
0858                     qCDebug(OkularCoreDebug) << "restored annot:" << annotation->uniqueName();
0859                     loadedAnything = true;
0860                 } else {
0861                     qCWarning(OkularCoreDebug).nospace() << "page (" << m_number << "): can't restore an annotation from XML.";
0862                 }
0863             }
0864 #ifdef PAGE_PROFILE
0865             qCDebug(OkularCoreDebug).nospace() << "annots: XML Load time: " << time.elapsed() << "ms";
0866 #endif
0867         }
0868         // parse formList child element
0869         else if (childElement.tagName() == QLatin1String("forms")) {
0870             // Clone forms as root node in restoredFormFieldList
0871             const QDomNode clonedNode = restoredFormFieldList.importNode(childElement, true);
0872             restoredFormFieldList.appendChild(clonedNode);
0873 
0874             if (formfields.isEmpty()) {
0875                 continue;
0876             }
0877 
0878             QHash<int, FormField *> hashedforms;
0879             for (FormField *ff : std::as_const(formfields)) {
0880                 hashedforms[ff->id()] = ff;
0881             }
0882 
0883             // iterate over all forms
0884             QDomNode formsNode = childElement.firstChild();
0885             while (formsNode.isElement()) {
0886                 // get annotation element and advance to next annot
0887                 QDomElement formElement = formsNode.toElement();
0888                 formsNode = formsNode.nextSibling();
0889 
0890                 if (formElement.tagName() != QLatin1String("form")) {
0891                     continue;
0892                 }
0893 
0894                 bool ok = true;
0895                 int index = formElement.attribute(QStringLiteral("id")).toInt(&ok);
0896                 if (!ok) {
0897                     continue;
0898                 }
0899 
0900                 QHash<int, FormField *>::const_iterator wantedIt = hashedforms.constFind(index);
0901                 if (wantedIt == hashedforms.constEnd()) {
0902                     continue;
0903                 }
0904 
0905                 QString value = formElement.attribute(QStringLiteral("value"));
0906                 (*wantedIt)->d_ptr->setValue(value);
0907                 loadedAnything = true;
0908             }
0909         }
0910     }
0911 
0912     return loadedAnything;
0913 }
0914 
0915 void PagePrivate::saveLocalContents(QDomNode &parentNode, QDomDocument &document, PageItems what) const
0916 {
0917     // create the page node and set the 'number' attribute
0918     QDomElement pageElement = document.createElement(QStringLiteral("page"));
0919     pageElement.setAttribute(QStringLiteral("number"), m_number);
0920 
0921     // add annotations info if has got any
0922     if ((what & AnnotationPageItems) && (what & OriginalAnnotationPageItems)) {
0923         const QDomElement savedDocRoot = restoredLocalAnnotationList.documentElement();
0924         if (!savedDocRoot.isNull()) {
0925             // Import and append node in target document
0926             const QDomNode importedNode = document.importNode(savedDocRoot, true);
0927             pageElement.appendChild(importedNode);
0928         }
0929     } else if ((what & AnnotationPageItems) && !m_page->m_annotations.isEmpty()) {
0930         // create the annotationList
0931         QDomElement annotListElement = document.createElement(QStringLiteral("annotationList"));
0932 
0933         // add every annotation to the annotationList
0934         for (const Annotation *a : std::as_const(m_page->m_annotations)) {
0935             // only save okular annotations (not the embedded in file ones)
0936             if (!(a->flags() & Annotation::External)) {
0937                 // append an filled-up element called 'annotation' to the list
0938                 QDomElement annElement = document.createElement(QStringLiteral("annotation"));
0939                 AnnotationUtils::storeAnnotation(a, annElement, document);
0940                 annotListElement.appendChild(annElement);
0941                 qCDebug(OkularCoreDebug) << "save annotation:" << a->uniqueName();
0942             }
0943         }
0944 
0945         // append the annotationList element if annotations have been set
0946         if (annotListElement.hasChildNodes()) {
0947             pageElement.appendChild(annotListElement);
0948         }
0949     }
0950 
0951     // add forms info if has got any
0952     if ((what & FormFieldPageItems) && (what & OriginalFormFieldPageItems)) {
0953         const QDomElement savedDocRoot = restoredFormFieldList.documentElement();
0954         if (!savedDocRoot.isNull()) {
0955             // Import and append node in target document
0956             const QDomNode importedNode = document.importNode(savedDocRoot, true);
0957             pageElement.appendChild(importedNode);
0958         }
0959     } else if ((what & FormFieldPageItems) && !formfields.isEmpty()) {
0960         // create the formList
0961         QDomElement formListElement = document.createElement(QStringLiteral("forms"));
0962 
0963         // add every form data to the formList
0964         for (const FormField *f : formfields) {
0965             QString newvalue = f->d_ptr->value();
0966             if (f->d_ptr->m_default == newvalue) {
0967                 continue;
0968             }
0969 
0970             // append an filled-up element called 'annotation' to the list
0971             QDomElement formElement = document.createElement(QStringLiteral("form"));
0972             formElement.setAttribute(QStringLiteral("id"), f->id());
0973             formElement.setAttribute(QStringLiteral("value"), newvalue);
0974             formListElement.appendChild(formElement);
0975         }
0976 
0977         // append the annotationList element if annotations have been set
0978         if (formListElement.hasChildNodes()) {
0979             pageElement.appendChild(formListElement);
0980         }
0981     }
0982 
0983     // append the page element only if has children
0984     if (pageElement.hasChildNodes()) {
0985         parentNode.appendChild(pageElement);
0986     }
0987 }
0988 
0989 const QPixmap *Page::_o_nearestPixmap(DocumentObserver *observer, int w, int h) const
0990 {
0991     Q_UNUSED(h)
0992 
0993     const QPixmap *pixmap = nullptr;
0994 
0995     // if a pixmap is present for given id, use it
0996     QMap<DocumentObserver *, PagePrivate::PixmapObject>::const_iterator itPixmap = d->m_pixmaps.constFind(observer);
0997     if (itPixmap != d->m_pixmaps.constEnd()) {
0998         pixmap = itPixmap.value().m_pixmap;
0999     } else if (!d->m_pixmaps.isEmpty()) {
1000         // else find the closest match using pixmaps of other IDs (great optim!)
1001         int minDistance = -1;
1002         QMap<DocumentObserver *, PagePrivate::PixmapObject>::const_iterator it = d->m_pixmaps.constBegin(), end = d->m_pixmaps.constEnd();
1003         for (; it != end; ++it) {
1004             int pixWidth = (*it).m_pixmap->width(), distance = pixWidth > w ? pixWidth - w : w - pixWidth;
1005             if (minDistance == -1 || distance < minDistance) {
1006                 pixmap = (*it).m_pixmap;
1007                 minDistance = distance;
1008             }
1009         }
1010     }
1011 
1012     return pixmap;
1013 }
1014 
1015 bool Page::hasTilesManager(const DocumentObserver *observer) const
1016 {
1017     return d->tilesManager(observer) != nullptr;
1018 }
1019 
1020 QList<Tile> Page::tilesAt(const DocumentObserver *observer, const NormalizedRect &rect) const
1021 {
1022     TilesManager *tm = d->m_tilesManagers.value(observer);
1023     if (tm) {
1024         return tm->tilesAt(rect, TilesManager::PixmapTile);
1025     } else {
1026         return QList<Tile>();
1027     }
1028 }
1029 
1030 TilesManager *PagePrivate::tilesManager(const DocumentObserver *observer) const
1031 {
1032     return m_tilesManagers.value(observer);
1033 }
1034 
1035 void PagePrivate::setTilesManager(const DocumentObserver *observer, TilesManager *tm)
1036 {
1037     TilesManager *old = m_tilesManagers.value(observer);
1038     delete old;
1039 
1040     m_tilesManagers.insert(observer, tm);
1041 }
1042 
1043 void PagePrivate::adoptGeneratedContents(PagePrivate *oldPage)
1044 {
1045     rotateAt(oldPage->m_rotation);
1046 
1047     m_pixmaps = oldPage->m_pixmaps;
1048     oldPage->m_pixmaps.clear();
1049 
1050     m_tilesManagers = oldPage->m_tilesManagers;
1051     oldPage->m_tilesManagers.clear();
1052 
1053     m_boundingBox = oldPage->m_boundingBox;
1054     m_isBoundingBoxKnown = oldPage->m_isBoundingBoxKnown;
1055     m_text = oldPage->m_text;
1056     oldPage->m_text = nullptr;
1057 
1058     m_textSelections = oldPage->m_textSelections;
1059     oldPage->m_textSelections = nullptr;
1060 
1061     restoredLocalAnnotationList = oldPage->restoredLocalAnnotationList;
1062     restoredFormFieldList = oldPage->restoredFormFieldList;
1063 }
1064 
1065 FormField *PagePrivate::findEquivalentForm(const Page *p, FormField *oldField)
1066 {
1067     // given how id is not very good of id (at least for pdf) we do a few passes
1068     // same rect, type and id
1069     for (FormField *f : std::as_const(p->d->formfields)) {
1070         if (f->rect() == oldField->rect() && f->type() == oldField->type() && f->id() == oldField->id()) {
1071             return f;
1072         }
1073     }
1074     // same rect and type
1075     for (FormField *f : std::as_const(p->d->formfields)) {
1076         if (f->rect() == oldField->rect() && f->type() == oldField->type()) {
1077             return f;
1078         }
1079     }
1080     // fuzzy rect, same type and id
1081     for (FormField *f : std::as_const(p->d->formfields)) {
1082         if (f->type() == oldField->type() && f->id() == oldField->id() && qFuzzyCompare(f->rect().left, oldField->rect().left) && qFuzzyCompare(f->rect().top, oldField->rect().top) &&
1083             qFuzzyCompare(f->rect().right, oldField->rect().right) && qFuzzyCompare(f->rect().bottom, oldField->rect().bottom)) {
1084             return f;
1085         }
1086     }
1087     // fuzzy rect and same type
1088     for (FormField *f : std::as_const(p->d->formfields)) {
1089         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) &&
1090             qFuzzyCompare(f->rect().bottom, oldField->rect().bottom)) {
1091             return f;
1092         }
1093     }
1094     return nullptr;
1095 }