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 }