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

KDE's Doxygen guidelines are available online.