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 }