Okular

page.cpp
1 /*
2  SPDX-FileCopyrightText: 2004 Enrico Ros <[email protected]>
3 
4  Work sponsored by the LiMux project of the city of Munich:
5  SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <[email protected]>
6 
7  SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include "page.h"
11 #include "page_p.h"
12 
13 // qt/kde includes
14 #include <QDomDocument>
15 #include <QDomElement>
16 #include <QHash>
17 #include <QPixmap>
18 #include <QSet>
19 #include <QString>
20 #include <QUuid>
21 #include <QVariant>
22 
23 #include <QDebug>
24 
25 // local includes
26 #include "action.h"
27 #include "annotations.h"
28 #include "annotations_p.h"
29 #include "debug_p.h"
30 #include "document.h"
31 #include "document_p.h"
32 #include "form.h"
33 #include "form_p.h"
34 #include "observer.h"
35 #include "pagecontroller_p.h"
36 #include "pagesize.h"
37 #include "pagetransition.h"
38 #include "rotationjob_p.h"
39 #include "textpage_p.h"
40 #include "tile.h"
41 #include "tilesmanager_p.h"
42 #include "utils_p.h"
43 
44 #include <limits>
45 
46 #ifdef PAGE_PROFILE
47 #include <QTime>
48 #endif
49 
50 using namespace Okular;
51 
52 static const double distanceConsideredEqual = 25; // 5px
53 
54 static void deleteObjectRects(QList<ObjectRect *> &rects, const QSet<ObjectRect::ObjectType> &which)
55 {
57  for (; it != rects.end();) {
58  if (which.contains((*it)->objectType())) {
59  delete *it;
60  it = rects.erase(it);
61  } else {
62  ++it;
63  }
64  }
65 }
66 
67 PagePrivate::PagePrivate(Page *page, uint n, double w, double h, Rotation o)
68  : m_page(page)
69  , m_number(n)
70  , m_orientation(o)
71  , m_width(w)
72  , m_height(h)
73  , m_doc(nullptr)
74  , m_boundingBox(0, 0, 1, 1)
75  , m_rotation(Rotation0)
76  , m_text(nullptr)
77  , m_transition(nullptr)
78  , m_textSelections(nullptr)
79  , m_openingAction(nullptr)
80  , m_closingAction(nullptr)
81  , m_duration(-1)
82  , m_isBoundingBoxKnown(false)
83 {
84  // avoid Division-By-Zero problems in the program
85  if (m_width <= 0) {
86  m_width = 1;
87  }
88 
89  if (m_height <= 0) {
90  m_height = 1;
91  }
92 }
93 
94 PagePrivate::~PagePrivate()
95 {
96  qDeleteAll(formfields);
97  delete m_openingAction;
98  delete m_closingAction;
99  delete m_text;
100  delete m_transition;
101 }
102 
103 PagePrivate *PagePrivate::get(Page *page)
104 {
105  return page ? page->d : nullptr;
106 }
107 
108 void PagePrivate::imageRotationDone(RotationJob *job)
109 {
110  TilesManager *tm = tilesManager(job->observer());
111  if (tm) {
112  QPixmap *pixmap = new QPixmap(QPixmap::fromImage(job->image()));
113  tm->setPixmap(pixmap, job->rect(), job->isPartialUpdate());
114  delete pixmap;
115  return;
116  }
117 
118  QMap<DocumentObserver *, PixmapObject>::iterator it = m_pixmaps.find(job->observer());
119  if (it != m_pixmaps.end()) {
120  PixmapObject &object = it.value();
121  (*object.m_pixmap) = QPixmap::fromImage(job->image());
122  object.m_rotation = job->rotation();
123  object.m_isPartialPixmap = job->isPartialUpdate();
124  } else {
125  PixmapObject object;
126  object.m_pixmap = new QPixmap(QPixmap::fromImage(job->image()));
127  object.m_rotation = job->rotation();
128  object.m_isPartialPixmap = job->isPartialUpdate();
129 
130  m_pixmaps.insert(job->observer(), object);
131  }
132 }
133 
134 QTransform PagePrivate::rotationMatrix() const
135 {
136  return Okular::buildRotationMatrix(m_rotation);
137 }
138 
139 /** class Page **/
140 
141 Page::Page(uint pageNumber, double w, double h, Rotation o)
142  : d(new PagePrivate(this, pageNumber, w, h, o))
143 {
144 }
145 
147 {
148  if (d) {
149  deletePixmaps();
150  deleteRects();
151  d->deleteHighlights();
153  d->deleteTextSelections();
155 
156  delete d;
157  }
158 }
159 
160 int Page::number() const
161 {
162  return d->m_number;
163 }
164 
166 {
167  return d->m_orientation;
168 }
169 
171 {
172  return d->m_rotation;
173 }
174 
176 {
177  return (Rotation)(((int)d->m_orientation + (int)d->m_rotation) % 4);
178 }
179 
180 double Page::width() const
181 {
182  return d->m_width;
183 }
184 
185 double Page::height() const
186 {
187  return d->m_height;
188 }
189 
190 double Page::ratio() const
191 {
192  return d->m_height / d->m_width;
193 }
194 
196 {
197  return d->m_boundingBox;
198 }
199 
201 {
202  return d->m_isBoundingBoxKnown;
203 }
204 
206 {
207  if (d->m_isBoundingBoxKnown && d->m_boundingBox == bbox) {
208  return;
209  }
210 
211  // Allow tiny rounding errors (happens during rotation)
212  static const double epsilon = 0.00001;
213  Q_ASSERT(bbox.left >= -epsilon && bbox.top >= -epsilon && bbox.right <= 1 + epsilon && bbox.bottom <= 1 + epsilon);
214 
215  d->m_boundingBox = bbox & NormalizedRect(0., 0., 1., 1.);
216  d->m_isBoundingBoxKnown = true;
217 }
218 
219 bool Page::hasPixmap(DocumentObserver *observer, int width, int height, const NormalizedRect &rect) const
220 {
221  TilesManager *tm = d->tilesManager(observer);
222  if (tm) {
223  if (width != tm->width() || height != tm->height()) {
224  return false;
225  }
226 
227  return tm->hasPixmap(rect);
228  }
229 
230  QMap<DocumentObserver *, PagePrivate::PixmapObject>::const_iterator it = d->m_pixmaps.constFind(observer);
231  if (it == d->m_pixmaps.constEnd()) {
232  return false;
233  }
234 
235  if (width == -1 || height == -1) {
236  return true;
237  }
238 
239  if (it.value().m_isPartialPixmap) {
240  return false;
241  }
242 
243  const QPixmap *pixmap = it.value().m_pixmap;
244 
245  return (pixmap->width() == width && pixmap->height() == height);
246 }
247 
248 void Page::setPageSize(DocumentObserver *observer, int width, int height)
249 {
250  TilesManager *tm = d->tilesManager(observer);
251  if (tm) {
252  tm->setSize(width, height);
253  }
254 }
255 
256 bool Page::hasTextPage() const
257 {
258  return d->m_text != nullptr;
259 }
260 
262 {
263  if (d->m_text) {
264  return d->m_text->wordAt(p, word);
265  }
266 
267  return nullptr;
268 }
269 
271 {
272  if (d->m_text) {
273  return d->m_text->textArea(selection);
274  }
275 
276  return nullptr;
277 }
278 
279 bool Page::hasObjectRect(double x, double y, double xScale, double yScale) const
280 {
281  if (m_rects.isEmpty()) {
282  return false;
283  }
284 
285  for (ObjectRect *rect : m_rects) {
286  if (rect->distanceSqr(x, y, xScale, yScale) < distanceConsideredEqual) {
287  return true;
288  }
289  }
290 
291  return false;
292 }
293 
294 bool Page::hasHighlights(int s_id) const
295 {
296  // simple case: have no highlights
297  if (m_highlights.isEmpty()) {
298  return false;
299  }
300  // simple case: we have highlights and no id to match
301  if (s_id == -1) {
302  return true;
303  }
304  // iterate on the highlights list to find an entry by id
305  for (HighlightAreaRect *highlight : m_highlights) {
306  if (highlight->s_id == s_id) {
307  return true;
308  }
309  }
310  return false;
311 }
312 
314 {
315  return d->m_transition != nullptr;
316 }
317 
319 {
320  return !m_annotations.isEmpty();
321 }
322 
323 RegularAreaRect *Page::findText(int id, const QString &text, SearchDirection direction, Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect) const
324 {
325  RegularAreaRect *rect = nullptr;
326  if (text.isEmpty() || !d->m_text) {
327  return rect;
328  }
329 
330  rect = d->m_text->findText(id, text, direction, caseSensitivity, lastRect);
331  return rect;
332 }
333 
335 {
337 }
338 
340 {
341  QString ret;
342 
343  if (!d->m_text) {
344  return ret;
345  }
346 
347  if (area) {
348  RegularAreaRect rotatedArea = *area;
349  rotatedArea.transform(d->rotationMatrix().inverted());
350 
351  ret = d->m_text->text(&rotatedArea, b);
352  } else {
353  ret = d->m_text->text(nullptr, b);
354  }
355 
356  return ret;
357 }
358 
360 {
361  TextEntity::List ret;
362 
363  if (!d->m_text) {
364  return ret;
365  }
366 
367  if (area) {
368  RegularAreaRect rotatedArea = *area;
369  rotatedArea.transform(d->rotationMatrix().inverted());
370 
371  ret = d->m_text->words(&rotatedArea, b);
372  } else {
373  ret = d->m_text->words(nullptr, b);
374  }
375 
376  for (auto &retI : ret) {
377  const TextEntity *orig = retI;
378  retI = new TextEntity(orig->text(), new Okular::NormalizedRect(orig->transformedArea(d->rotationMatrix())));
379  delete orig;
380  }
381 
382  return ret;
383 }
384 
385 void PagePrivate::rotateAt(Rotation orientation)
386 {
387  if (orientation == m_rotation) {
388  return;
389  }
390 
391  deleteTextSelections();
392 
393  if (((int)m_orientation + (int)m_rotation) % 2 != ((int)m_orientation + (int)orientation) % 2) {
394  std::swap(m_width, m_height);
395  }
396 
397  Rotation oldRotation = m_rotation;
398  m_rotation = orientation;
399 
400  /**
401  * Rotate the images of the page.
402  */
404  while (it.hasNext()) {
405  it.next();
406 
407  const PagePrivate::PixmapObject &object = it.value();
408 
409  RotationJob *job = new RotationJob(object.m_pixmap->toImage(), object.m_rotation, m_rotation, it.key());
410  job->setPage(this);
411  m_doc->m_pageController->addRotationJob(job);
412  }
413 
414  /**
415  * Rotate tiles manager
416  */
418  while (i.hasNext()) {
419  i.next();
420 
421  TilesManager *tm = i.value();
422  if (tm) {
423  tm->setRotation(m_rotation);
424  }
425  }
426 
427  /**
428  * Rotate the object rects on the page.
429  */
430  const QTransform matrix = rotationMatrix();
431  for (ObjectRect *objRect : qAsConst(m_page->m_rects)) {
432  objRect->transform(matrix);
433  }
434 
435  const QTransform highlightRotationMatrix = Okular::buildRotationMatrix((Rotation)(((int)m_rotation - (int)oldRotation + 4) % 4));
436  for (HighlightAreaRect *hlar : qAsConst(m_page->m_highlights)) {
437  hlar->transform(highlightRotationMatrix);
438  }
439 }
440 
441 void PagePrivate::changeSize(const PageSize &size)
442 {
443  if (size.isNull() || (size.width() == m_width && size.height() == m_height)) {
444  return;
445  }
446 
447  m_page->deletePixmaps();
448  // deleteHighlights();
449  // deleteTextSelections();
450 
451  m_width = size.width();
452  m_height = size.height();
453  if (m_rotation % 2) {
454  std::swap(m_width, m_height);
455  }
456 }
457 
458 const ObjectRect *Page::objectRect(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale) const
459 {
460  // Walk list in reverse order so that annotations in the foreground are preferred
461  QListIterator<ObjectRect *> it(m_rects);
462  it.toBack();
463  while (it.hasPrevious()) {
464  const ObjectRect *objrect = it.previous();
465  if ((objrect->objectType() == type) && objrect->distanceSqr(x, y, xScale, yScale) < distanceConsideredEqual) {
466  return objrect;
467  }
468  }
469 
470  return nullptr;
471 }
472 
473 QList<const ObjectRect *> Page::objectRects(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale) const
474 {
476 
477  QListIterator<ObjectRect *> it(m_rects);
478  it.toBack();
479  while (it.hasPrevious()) {
480  const ObjectRect *objrect = it.previous();
481  if ((objrect->objectType() == type) && objrect->distanceSqr(x, y, xScale, yScale) < distanceConsideredEqual) {
482  result.append(objrect);
483  }
484  }
485 
486  return result;
487 }
488 
489 const ObjectRect *Page::nearestObjectRect(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale, double *distance) const
490 {
491  ObjectRect *res = nullptr;
492  double minDistance = std::numeric_limits<double>::max();
493 
494  for (ObjectRect *rect : m_rects) {
495  if (rect->objectType() == type) {
496  double d = rect->distanceSqr(x, y, xScale, yScale);
497  if (d < minDistance) {
498  res = rect;
499  minDistance = d;
500  }
501  }
502  }
503 
504  if (distance) {
505  *distance = minDistance;
506  }
507  return res;
508 }
509 
511 {
512  return d->m_transition;
513 }
514 
516 {
517  return m_annotations;
518 }
519 
520 Annotation *Page::annotation(const QString &uniqueName) const
521 {
522  for (Annotation *a : m_annotations) {
523  if (a->uniqueName() == uniqueName) {
524  return a;
525  }
526  }
527  return nullptr;
528 }
529 
530 const Action *Page::pageAction(PageAction action) const
531 {
532  switch (action) {
533  case Page::Opening:
534  return d->m_openingAction;
535  break;
536  case Page::Closing:
537  return d->m_closingAction;
538  break;
539  }
540 
541  return nullptr;
542 }
543 
545 {
546  return d->formfields;
547 }
548 
549 void Page::setPixmap(DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect)
550 {
551  d->setPixmap(observer, pixmap, rect, false /*isPartialPixmap*/);
552 }
553 
554 void PagePrivate::setPixmap(DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect, bool isPartialPixmap)
555 {
556  if (m_rotation == Rotation0) {
557  TilesManager *tm = tilesManager(observer);
558  if (tm) {
559  tm->setPixmap(pixmap, rect, isPartialPixmap);
560  delete pixmap;
561  return;
562  }
563 
565  if (it != m_pixmaps.end()) {
566  delete it.value().m_pixmap;
567  } else {
568  it = m_pixmaps.insert(observer, PagePrivate::PixmapObject());
569  }
570  it.value().m_pixmap = pixmap;
571  it.value().m_rotation = m_rotation;
572  it.value().m_isPartialPixmap = isPartialPixmap;
573  } else {
574  // it can happen that we get a setPixmap while closing and thus the page controller is gone
575  if (m_doc->m_pageController) {
576  RotationJob *job = new RotationJob(pixmap->toImage(), Rotation0, m_rotation, observer);
577  job->setPage(this);
578  job->setRect(TilesManager::toRotatedRect(rect, m_rotation));
579  job->setIsPartialUpdate(isPartialPixmap);
580  m_doc->m_pageController->addRotationJob(job);
581  }
582 
583  delete pixmap;
584  }
585 }
586 
588 {
589  delete d->m_text;
590 
591  d->m_text = textPage;
592  if (d->m_text) {
593  d->m_text->d->m_page = this;
594  // Correct/optimize text order for search and text selection
595  d->m_text->d->correctTextOrder();
596  }
597 }
598 
600 {
603  deleteObjectRects(m_rects, which);
604 
605  /**
606  * Rotate the object rects of the page.
607  */
608  const QTransform matrix = d->rotationMatrix();
609 
610  for (ObjectRect *objectRect : rects) {
611  objectRect->transform(matrix);
612  }
613 
614  m_rects << rects;
615 }
616 
618 {
619  return m_rects;
620 }
621 
622 void PagePrivate::setHighlight(int s_id, RegularAreaRect *rect, const QColor &color)
623 {
624  HighlightAreaRect *hr = new HighlightAreaRect(rect);
625  hr->s_id = s_id;
626  hr->color = color;
627 
628  m_page->m_highlights.append(hr);
629 }
630 
631 void PagePrivate::setTextSelections(RegularAreaRect *r, const QColor &color)
632 {
633  deleteTextSelections();
634  if (r) {
636  hr->s_id = -1;
637  hr->color = color;
638  m_textSelections = hr;
639  delete r;
640  }
641 }
642 
644 {
646  for (SourceRefObjectRect *rect : refRects) {
647  m_rects << rect;
648  }
649 }
650 
651 void Page::setDuration(double seconds)
652 {
653  d->m_duration = seconds;
654 }
655 
656 double Page::duration() const
657 {
658  return d->m_duration;
659 }
660 
661 void Page::setLabel(const QString &label)
662 {
663  d->m_label = label;
664 }
665 
667 {
668  return d->m_label;
669 }
670 
672 {
673  return d->m_textSelections;
674 }
675 
677 {
678  return d->m_textSelections ? d->m_textSelections->color : QColor();
679 }
680 
682 {
683  // Generate uniqueName: okular-{UUID}
684  if (annotation->uniqueName().isEmpty()) {
685  QString uniqueName = QStringLiteral("okular-") + QUuid::createUuid().toString();
686  annotation->setUniqueName(uniqueName);
687  }
688  annotation->d_ptr->m_page = d;
689  m_annotations.append(annotation);
690 
692 
693  // Rotate the annotation on the page.
694  const QTransform matrix = d->rotationMatrix();
695  annotation->d_ptr->annotationTransform(matrix);
696 
697  m_rects.append(rect);
698 }
699 
701 {
702  if (!d->m_doc->m_parent->canRemovePageAnnotation(annotation)) {
703  return false;
704  }
705 
706  QList<Annotation *>::iterator aIt = m_annotations.begin();
707  for (; aIt != m_annotations.end(); ++aIt) {
708  if ((*aIt) && (*aIt)->uniqueName() == annotation->uniqueName()) {
709  int rectfound = false;
710  QList<ObjectRect *>::iterator it = m_rects.begin();
711  for (; it != m_rects.end() && !rectfound; ++it) {
712  if (((*it)->objectType() == ObjectRect::OAnnotation) && ((*it)->object() == (*aIt))) {
713  delete *it;
714  it = m_rects.erase(it);
715  rectfound = true;
716  }
717  }
718  qCDebug(OkularCoreDebug) << "removed annotation:" << annotation->uniqueName();
719  annotation->d_ptr->m_page = nullptr;
720  m_annotations.erase(aIt);
721  break;
722  }
723  }
724 
725  return true;
726 }
727 
729 {
730  delete d->m_transition;
731  d->m_transition = transition;
732 }
733 
735 {
736  switch (action) {
737  case Page::Opening:
738  delete d->m_openingAction;
739  d->m_openingAction = link;
740  break;
741  case Page::Closing:
742  delete d->m_closingAction;
743  d->m_closingAction = link;
744  break;
745  }
746 }
747 
749 {
750  qDeleteAll(d->formfields);
751  d->formfields = fields;
752  for (FormField *ff : qAsConst(d->formfields)) {
753  ff->d_ptr->setDefault();
754  ff->d_ptr->m_page = this;
755  }
756 }
757 
759 {
760  TilesManager *tm = d->tilesManager(observer);
761  if (tm) {
762  delete tm;
763  d->m_tilesManagers.remove(observer);
764  } else {
765  PagePrivate::PixmapObject object = d->m_pixmaps.take(observer);
766  delete object.m_pixmap;
767  }
768 }
769 
771 {
773  while (it.hasNext()) {
774  it.next();
775  delete it.value().m_pixmap;
776  }
777 
778  d->m_pixmaps.clear();
779 
780  qDeleteAll(d->m_tilesManagers);
781  d->m_tilesManagers.clear();
782 }
783 
785 {
786  // delete ObjectRects of type Link and Image
789  deleteObjectRects(m_rects, which);
790 }
791 
792 void PagePrivate::deleteHighlights(int s_id)
793 {
794  // delete highlights by ID
795  QList<HighlightAreaRect *>::iterator it = m_page->m_highlights.begin();
796  while (it != m_page->m_highlights.end()) {
797  HighlightAreaRect *highlight = *it;
798  if (s_id == -1 || highlight->s_id == s_id) {
799  it = m_page->m_highlights.erase(it);
800  delete highlight;
801  } else {
802  ++it;
803  }
804  }
805 }
806 
807 void PagePrivate::deleteTextSelections()
808 {
809  delete m_textSelections;
810  m_textSelections = nullptr;
811 }
812 
814 {
815  deleteObjectRects(m_rects, QSet<ObjectRect::ObjectType>() << ObjectRect::SourceRef);
816 }
817 
819 {
820  // delete ObjectRects of type Annotation
821  deleteObjectRects(m_rects, QSet<ObjectRect::ObjectType>() << ObjectRect::OAnnotation);
822  // delete all stored annotations
823  qDeleteAll(m_annotations);
824  m_annotations.clear();
825 }
826 
827 bool PagePrivate::restoreLocalContents(const QDomNode &pageNode)
828 {
829  bool loadedAnything = false; // set if something actually gets loaded
830 
831  // iterate over all children (annotationList, ...)
832  QDomNode childNode = pageNode.firstChild();
833  while (childNode.isElement()) {
834  QDomElement childElement = childNode.toElement();
835  childNode = childNode.nextSibling();
836 
837  // parse annotationList child element
838  if (childElement.tagName() == QLatin1String("annotationList")) {
839 #ifdef PAGE_PROFILE
840  QTime time;
841  time.start();
842 #endif
843  // Clone annotationList as root node in restoredLocalAnnotationList
844  const QDomNode clonedNode = restoredLocalAnnotationList.importNode(childElement, true);
845  restoredLocalAnnotationList.appendChild(clonedNode);
846 
847  // iterate over all annotations
848  QDomNode annotationNode = childElement.firstChild();
849  while (annotationNode.isElement()) {
850  // get annotation element and advance to next annot
851  QDomElement annotElement = annotationNode.toElement();
852  annotationNode = annotationNode.nextSibling();
853 
854  // get annotation from the dom element
855  Annotation *annotation = AnnotationUtils::createAnnotation(annotElement);
856 
857  // append annotation to the list or show warning
858  if (annotation) {
859  m_doc->performAddPageAnnotation(m_number, annotation);
860  qCDebug(OkularCoreDebug) << "restored annot:" << annotation->uniqueName();
861  loadedAnything = true;
862  } else {
863  qCWarning(OkularCoreDebug).nospace() << "page (" << m_number << "): can't restore an annotation from XML.";
864  }
865  }
866 #ifdef PAGE_PROFILE
867  qCDebug(OkularCoreDebug).nospace() << "annots: XML Load time: " << time.elapsed() << "ms";
868 #endif
869  }
870  // parse formList child element
871  else if (childElement.tagName() == QLatin1String("forms")) {
872  // Clone forms as root node in restoredFormFieldList
873  const QDomNode clonedNode = restoredFormFieldList.importNode(childElement, true);
874  restoredFormFieldList.appendChild(clonedNode);
875 
876  if (formfields.isEmpty()) {
877  continue;
878  }
879 
880  QHash<int, FormField *> hashedforms;
881  for (FormField *ff : qAsConst(formfields)) {
882  hashedforms[ff->id()] = ff;
883  }
884 
885  // iterate over all forms
886  QDomNode formsNode = childElement.firstChild();
887  while (formsNode.isElement()) {
888  // get annotation element and advance to next annot
889  QDomElement formElement = formsNode.toElement();
890  formsNode = formsNode.nextSibling();
891 
892  if (formElement.tagName() != QLatin1String("form")) {
893  continue;
894  }
895 
896  bool ok = true;
897  int index = formElement.attribute(QStringLiteral("id")).toInt(&ok);
898  if (!ok) {
899  continue;
900  }
901 
902  QHash<int, FormField *>::const_iterator wantedIt = hashedforms.constFind(index);
903  if (wantedIt == hashedforms.constEnd()) {
904  continue;
905  }
906 
907  QString value = formElement.attribute(QStringLiteral("value"));
908  (*wantedIt)->d_ptr->setValue(value);
909  loadedAnything = true;
910  }
911  }
912  }
913 
914  return loadedAnything;
915 }
916 
917 void PagePrivate::saveLocalContents(QDomNode &parentNode, QDomDocument &document, PageItems what) const
918 {
919  // create the page node and set the 'number' attribute
920  QDomElement pageElement = document.createElement(QStringLiteral("page"));
921  pageElement.setAttribute(QStringLiteral("number"), m_number);
922 
923  // add annotations info if has got any
924  if ((what & AnnotationPageItems) && (what & OriginalAnnotationPageItems)) {
925  const QDomElement savedDocRoot = restoredLocalAnnotationList.documentElement();
926  if (!savedDocRoot.isNull()) {
927  // Import and append node in target document
928  const QDomNode importedNode = document.importNode(savedDocRoot, true);
929  pageElement.appendChild(importedNode);
930  }
931  } else if ((what & AnnotationPageItems) && !m_page->m_annotations.isEmpty()) {
932  // create the annotationList
933  QDomElement annotListElement = document.createElement(QStringLiteral("annotationList"));
934 
935  // add every annotation to the annotationList
936  for (const Annotation *a : qAsConst(m_page->m_annotations)) {
937  // only save okular annotations (not the embedded in file ones)
938  if (!(a->flags() & Annotation::External)) {
939  // append an filled-up element called 'annotation' to the list
940  QDomElement annElement = document.createElement(QStringLiteral("annotation"));
941  AnnotationUtils::storeAnnotation(a, annElement, document);
942  annotListElement.appendChild(annElement);
943  qCDebug(OkularCoreDebug) << "save annotation:" << a->uniqueName();
944  }
945  }
946 
947  // append the annotationList element if annotations have been set
948  if (annotListElement.hasChildNodes()) {
949  pageElement.appendChild(annotListElement);
950  }
951  }
952 
953  // add forms info if has got any
954  if ((what & FormFieldPageItems) && (what & OriginalFormFieldPageItems)) {
955  const QDomElement savedDocRoot = restoredFormFieldList.documentElement();
956  if (!savedDocRoot.isNull()) {
957  // Import and append node in target document
958  const QDomNode importedNode = document.importNode(savedDocRoot, true);
959  pageElement.appendChild(importedNode);
960  }
961  } else if ((what & FormFieldPageItems) && !formfields.isEmpty()) {
962  // create the formList
963  QDomElement formListElement = document.createElement(QStringLiteral("forms"));
964 
965  // add every form data to the formList
966  for (const FormField *f : formfields) {
967  QString newvalue = f->d_ptr->value();
968  if (f->d_ptr->m_default == newvalue) {
969  continue;
970  }
971 
972  // append an filled-up element called 'annotation' to the list
973  QDomElement formElement = document.createElement(QStringLiteral("form"));
974  formElement.setAttribute(QStringLiteral("id"), f->id());
975  formElement.setAttribute(QStringLiteral("value"), newvalue);
976  formListElement.appendChild(formElement);
977  }
978 
979  // append the annotationList element if annotations have been set
980  if (formListElement.hasChildNodes()) {
981  pageElement.appendChild(formListElement);
982  }
983  }
984 
985  // append the page element only if has children
986  if (pageElement.hasChildNodes()) {
987  parentNode.appendChild(pageElement);
988  }
989 }
990 
991 const QPixmap *Page::_o_nearestPixmap(DocumentObserver *observer, int w, int h) const
992 {
993  Q_UNUSED(h)
994 
995  const QPixmap *pixmap = nullptr;
996 
997  // if a pixmap is present for given id, use it
998  QMap<DocumentObserver *, PagePrivate::PixmapObject>::const_iterator itPixmap = d->m_pixmaps.constFind(observer);
999  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 : qAsConst(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 : qAsConst(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 : qAsConst(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 : qAsConst(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 }
void setLabel(const QString &label)
Sets the labels for the page to label .
Definition: page.cpp:661
void append(const T &value)
void deleteAnnotations()
Deletes all annotations of the page.
Definition: page.cpp:818
bool isNull() const
Whether the page size is null.
Definition: pagesize.cpp:79
This class describes the object rectangle for a source reference.
Definition: area.h:598
Collector for all the data belonging to a page.
Definition: page.h:47
@ Closing
An action to be executed when the page is "closed".
Definition: page.h:55
void setBoundingBox(const NormalizedRect &bbox)
Sets the bounding box of the page content in normalized [0,1] coordinates, in terms of the upright or...
Definition: page.cpp:205
ObjectType objectType() const
Returns the object type of the object rectangle.
Definition: area.cpp:352
void setObjectRects(const QList< ObjectRect * > &rects)
Sets the list of object rects of the page.
Definition: page.cpp:599
The base interface of a form field.
Definition: form.h:39
QDomNode firstChild() const const
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
QDomElement toElement() const const
const ObjectRect * nearestObjectRect(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale, double *distance) const
Returns the object rect of the given type which is nearest to the point (x, y) at scale (xScale,...
Definition: page.cpp:489
const T & value() const const
NormalizedRect transformedArea(const QTransform &matrix) const
Returns the transformed area of the text entity.
Definition: textpage.cpp:205
RegularAreaRect * wordAt(const NormalizedPoint &p, QString *word=nullptr) const
Returns the area and text of the word at the given point Note that ownership of the returned area bel...
Definition: page.cpp:261
@ Image
An image.
Definition: area.h:465
const Action * pageAction(PageAction action) const
Returns the Action object which is associated with the given page action or 0 if no page action is se...
Definition: page.cpp:530
CaseSensitivity
The documentation to the global Okular namespace.
Definition: action.h:16
QDomNode importNode(const QDomNode &importedNode, bool deep)
QString tagName() const const
bool hasPrevious() const const
Annotation struct holds properties shared by all annotations.
Definition: annotations.h:95
static void storeAnnotation(const Annotation *annotation, QDomElement &element, QDomDocument &document)
Saves the annotation as a child of element taking care of saving all revisions if it has any.
virtual int id() const =0
The ID of the field.
Encapsulates data that describes an action.
Definition: action.h:40
virtual void transform(const QTransform &matrix)
Transforms the object rectangle with the operations defined by matrix.
Definition: area.cpp:379
bool isNull() const const
SearchDirection
Describes the direction of searching.
Definition: global.h:35
NormalizedPoint is a helper class which stores the coordinates of a normalized point.
Definition: area.h:116
Base class for objects being notified when something changes.
Definition: observer.h:28
Information object for the transition effect of a page.
bool hasNext() const const
double duration() const
Returns the duration in seconds of the page when displayed in presentation mode.
Definition: page.cpp:656
This class describes the object rectangle for an annotation.
Definition: area.h:555
QDomElement createElement(const QString &tagName)
const ObjectRect * objectRect(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale) const
Returns the object rect of the given type which is at point (x, y) at scale (xScale,...
Definition: page.cpp:458
QColor color
The color of the highlight.
Definition: area.h:967
@ Opening
An action to be executed when the page is "opened".
Definition: page.h:54
~Page()
Destroys the page.
Definition: page.cpp:146
Rotation rotation() const
Returns the rotation of the page as defined by the user.
Definition: page.cpp:170
QString text(const RegularAreaRect *area=nullptr) const
Returns the page text (or part of it).
Definition: page.cpp:334
Rotation orientation() const
Returns the orientation of the page as defined by the document.
Definition: page.cpp:165
void setAttribute(const QString &name, const QString &value)
const PageTransition * transition() const
Returns the transition effect of the page or 0 if no transition effect is set (see hasTransition()).
Definition: page.cpp:510
Represents the textual information of a Page.
Definition: textpage.h:105
void setSourceReferences(const QList< SourceRefObjectRect * > &rects)
Sets the list of source reference objects rects.
Definition: page.cpp:643
TextAreaInclusionBehaviour
Defines the behaviour of adding characters to text() result.
Definition: textpage.h:117
const T & previous()
void deletePixmap(DocumentObserver *observer)
Deletes the pixmap for the given observer.
Definition: page.cpp:758
void setPixmap(DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect=NormalizedRect())
Sets the region described by rect with pixmap for the given observer.
Definition: page.cpp:549
Wrapper around the information needed to generate the selection area There are two assumptions inside...
Definition: misc.h:33
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
RegularAreaRect * textArea(TextSelection *selection) const
Returns the rectangular area of the given selection.
Definition: page.cpp:270
void addAnnotation(Annotation *annotation)
Adds a new annotation to the page.
Definition: page.cpp:681
This is a list of NormalizedRect, to describe an area consisting of multiple rectangles using normali...
Definition: area.h:932
QString text() const
Returns the text of the text entity.
Definition: textpage.cpp:195
bool hasTransition() const
Returns whether the page provides a transition effect.
Definition: page.cpp:313
QHash::const_iterator constEnd() const const
@ Action
An action.
Definition: area.h:464
bool isEmpty() const const
QUuid createUuid()
This class stores the geometry of a highlighting area in normalized coordinates, together with highli...
Definition: area.h:950
QString uniqueName() const
Returns the unique name of the annotation.
int s_id
The search ID of the highlight owner.
Definition: area.h:962
const RegularAreaRect * textSelection() const
Returns the current text selection.
Definition: page.cpp:671
@ Rotation0
Not rotated.
Definition: global.h:46
double ratio() const
Returns the ration (height / width) of the page.
Definition: page.cpp:190
double width() const
Returns the width of the page.
Definition: page.cpp:180
void setTextPage(TextPage *text)
Sets the text page.
Definition: page.cpp:587
int toInt(bool *ok, int base) const const
TextEntity::List words(const RegularAreaRect *area, TextPage::TextAreaInclusionBehaviour b) const
Returns the page text (or part of it) including the bounding rectangles.
Definition: page.cpp:359
void setUniqueName(const QString &name)
Sets the unique name of the annotation.
bool hasObjectRect(double x, double y, double xScale, double yScale) const
Returns whether the page has an object rect which includes the point (x, y) at scale (xScale,...
Definition: page.cpp:279
virtual NormalizedRect rect() const =0
The bounding rect of the field, in normalized coordinates.
bool hasChildNodes() const const
int height() const const
QHash::const_iterator constFind(const Key &key) const const
A NormalizedRect is a rectangle which can be defined by two NormalizedPoints.
Definition: area.h:188
void setTransition(PageTransition *transition)
Sets the page transition effect.
Definition: page.cpp:728
QImage toImage() const const
void setFormFields(const QList< FormField * > &fields)
Sets fields as list of FormField of the page.
Definition: page.cpp:748
QString toString() const const
double left
The normalized left coordinate.
Definition: area.h:420
@ OAnnotation
An annotation.
Definition: area.h:466
bool contains(const T &value) const const
static Annotation * createAnnotation(const QDomElement &element)
Restore an annotation (with revisions if needed) from the dom element.
Definition: annotations.cpp:92
QList< FormField * > formFields() const
Returns the list of FormField of the page.
Definition: page.cpp:544
double width() const
Returns the width of the page size.
Definition: pagesize.cpp:52
QMapIterator::Item next()
void transform(const QTransform &matrix)
Transforms the regular area with the operations defined by matrix.
Definition: area.h:909
double right
The normalized right coordinate.
Definition: area.h:430
QList::iterator erase(QList::iterator pos)
void start()
double height() const
Returns the height of the page size.
Definition: pagesize.cpp:61
QDomNode appendChild(const QDomNode &newChild)
Annotation * annotation(const QString &uniqueName) const
Returns the annotation with the given unique name.
Definition: page.cpp:520
int elapsed() const const
QString label() const
Returns the label of the page, or a null string if not set.
Definition: page.cpp:666
void setPageAction(PageAction action, Action *link)
Sets the link object for the given page action.
Definition: page.cpp:734
int number() const
Returns the number of the page in the document.
Definition: page.cpp:160
ObjectType
Describes the type of storable object.
Definition: area.h:463
NormalizedRect boundingBox() const
Returns the bounding box of the page content in normalized [0,1] coordinates, in terms of the upright...
Definition: page.cpp:195
QColor textSelectionColor() const
Returns the color of the current text selection, or an invalid color if no text selection has been se...
Definition: page.cpp:676
void deleteSourceReferences()
Deletes all source reference objects of the page.
Definition: page.cpp:813
Page(uint pageNumber, double width, double height, Rotation orientation)
Creates a new page.
Definition: page.cpp:141
QDomNode nextSibling() const const
QList::iterator begin()
double distanceSqr(double x, double y, double xScale, double yScale) const
Returns the squared distance between the object and the point with normalized coordinates (x,...
Definition: area.cpp:384
bool hasHighlights(int id=-1) const
Returns whether the page provides highlighting for the observer with the given id.
Definition: page.cpp:294
void setDuration(double seconds)
Sets the duration of the page to seconds when displayed in presentation mode.
Definition: page.cpp:651
FieldType type() const
The type of the field.
Definition: form.cpp:46
void deleteRects()
Deletes all object rects of the page.
Definition: page.cpp:784
@ External
Is stored external.
Definition: annotations.h:138
PageAction
An action to be executed when particular events happen.
Definition: page.h:53
QString attribute(const QString &name, const QString &defValue) const const
Represents a piece of text on a TextPage, containing its textual representation and its bounding box.
Definition: textpage.h:51
bool removeAnnotation(Annotation *annotation)
Removes the annotation from the page.
Definition: page.cpp:700
void deletePixmaps()
Deletes all pixmaps of the page.
Definition: page.cpp:770
@ SourceRef
A source reference.
Definition: area.h:467
bool isElement() const const
double height() const
Returns the height of the page.
Definition: page.cpp:185
RegularAreaRect * findText(int id, const QString &text, SearchDirection direction, Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect=nullptr) const
Returns the bounding rect of the text which matches the following criteria or 0 if the search is not ...
Definition: page.cpp:323
QList::iterator end()
A small class that represents the size of a page.
Definition: pagesize.h:23
bool hasTilesManager(const DocumentObserver *observer) const
Returns whether pixmaps for the tiled observer are handled by a tile manager.
Definition: page.cpp:1017
@ AnyPixelTextAreaInclusionBehaviour
A character is included into text() result if any pixel of his bounding box is in the given area.
Definition: textpage.h:118
QList< Annotation * > annotations() const
Returns the list of annotations of the page.
Definition: page.cpp:515
An area with normalized coordinates that contains a reference to an object.
Definition: area.h:457
void setPageSize(DocumentObserver *observer, int width, int height)
Sets the size of the page (in screen pixels) if there is a TilesManager.
Definition: page.cpp:248
Rotation totalOrientation() const
Returns the total orientation which is the original orientation plus the user defined rotation.
Definition: page.cpp:175
const QList< ObjectRect * > & objectRects() const
Gets the list of object rects of the page.
Definition: page.cpp:617
QList< Tile > tilesAt(const DocumentObserver *observer, const NormalizedRect &rect) const
Returns a list of all tiles intersecting with rect.
Definition: page.cpp:1022
const QList< QKeySequence > & end()
int width() const const
bool hasAnnotations() const
Returns whether the page provides annotations.
Definition: page.cpp:318
double top
The normalized top coordinate.
Definition: area.h:425
double bottom
The normalized bottom coordinate.
Definition: area.h:435
virtual QVariant get(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName)
bool isBoundingBoxKnown() const
Returns whether the bounding box of the page has been computed.
Definition: page.cpp:200
Rotation
A rotation.
Definition: global.h:45
bool hasPixmap(DocumentObserver *observer, int width=-1, int height=-1, const NormalizedRect &rect=NormalizedRect()) const
Returns whether the page of size width x height has a pixmap in the region given by rect for the give...
Definition: page.cpp:219
bool hasTextPage() const
Returns whether the page provides a text page (TextPage).
Definition: page.cpp:256
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Thu Mar 23 2023 04:04:24 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.