1 /*
2     SPDX-FileCopyrightText: 2004-05 Enrico Ros <eros.kde@email.it>
3     SPDX-FileCopyrightText: 2005 Piotr Szymanski <niedakh@gmail.com>
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "area.h"
8 
9 #include <QPolygonF>
10 #include <QRect>
11 
12 #include "action.h"
13 #include "annotations.h"
14 #include "annotations_p.h"
15 #include "debug_p.h"
16 #include "sourcereference.h"
17 
18 using namespace Okular;
19 
20 /** class NormalizedPoint **/
NormalizedPoint()21 NormalizedPoint::NormalizedPoint()
22     : x(0.0)
23     , y(0.0)
24 {
25 }
26 
NormalizedPoint(double dX,double dY)27 NormalizedPoint::NormalizedPoint(double dX, double dY)
28     : x(dX)
29     , y(dY)
30 {
31 }
32 
NormalizedPoint(int iX,int iY,int xScale,int yScale)33 NormalizedPoint::NormalizedPoint(int iX, int iY, int xScale, int yScale)
34     : x((double)iX / (double)xScale)
35     , y((double)iY / (double)yScale)
36 {
37 }
38 
39 NormalizedPoint &NormalizedPoint::operator=(const NormalizedPoint &p) = default;
40 NormalizedPoint::NormalizedPoint(const NormalizedPoint &) = default;
41 NormalizedPoint::~NormalizedPoint() = default;
42 
transform(const QTransform & matrix)43 void NormalizedPoint::transform(const QTransform &matrix)
44 {
45     qreal tmp_x = (qreal)x;
46     qreal tmp_y = (qreal)y;
47     matrix.map(tmp_x, tmp_y, &tmp_x, &tmp_y);
48     x = tmp_x;
49     y = tmp_y;
50 }
51 
distanceSqr(double x,double y,double xScale,double yScale) const52 double NormalizedPoint::distanceSqr(double x, double y, double xScale, double yScale) const
53 {
54     return pow((this->x - x) * xScale, 2) + pow((this->y - y) * yScale, 2);
55 }
56 
57 /**
58  * Returns a vector from the given points @p a and @p b
59  * @internal
60  */
operator -(const NormalizedPoint & a,const NormalizedPoint & b)61 NormalizedPoint operator-(const NormalizedPoint &a, const NormalizedPoint &b)
62 {
63     return NormalizedPoint(a.x - b.x, a.y - b.y);
64 }
65 
66 /**
67  * @brief Calculates distance of the point @p x @p y @p xScale @p yScale to the line segment from @p start to @p end
68  */
distanceSqr(double x,double y,double xScale,double yScale,const NormalizedPoint & start,const NormalizedPoint & end)69 double NormalizedPoint::distanceSqr(double x, double y, double xScale, double yScale, const NormalizedPoint &start, const NormalizedPoint &end)
70 {
71     NormalizedPoint point(x, y);
72     double thisDistance;
73     NormalizedPoint lineSegment(end - start);
74     const double lengthSqr = pow(lineSegment.x, 2) + pow(lineSegment.y, 2);
75 
76     // if the length of the current segment is null, we can just
77     // measure the distance to either end point
78     if (lengthSqr == 0.0) {
79         thisDistance = end.distanceSqr(x, y, xScale, yScale);
80     } else {
81         // vector from the start point of the current line segment to the measurement point
82         NormalizedPoint a = point - start;
83         // vector from the same start point to the end point of the current line segment
84         NormalizedPoint b = end - start;
85 
86         // we're using a * b (dot product) := |a| * |b| * cos(phi) and the knowledge
87         // that cos(phi) is adjacent side / hypotenuse (hypotenuse = |b|)
88         // therefore, t becomes the length of the vector that represents the projection of
89         // the point p onto the current line segment
90         //(hint: if this is still unclear, draw it!)
91         float t = (a.x * b.x + a.y * b.y) / lengthSqr;
92 
93         if (t < 0) {
94             // projection falls outside the line segment on the side of "start"
95             thisDistance = point.distanceSqr(start.x, start.y, xScale, yScale);
96         } else if (t > 1) {
97             // projection falls outside the line segment on the side of the current point
98             thisDistance = point.distanceSqr(end.x, end.y, xScale, yScale);
99         } else {
100             // projection is within [start, *i];
101             // determine the length of the perpendicular distance from the projection to the actual point
102             NormalizedPoint direction = end - start;
103             NormalizedPoint projection = start - NormalizedPoint(-t * direction.x, -t * direction.y);
104             thisDistance = projection.distanceSqr(x, y, xScale, yScale);
105         }
106     }
107     return thisDistance;
108 }
109 
operator <<(QDebug str,const Okular::NormalizedPoint & p)110 QDebug operator<<(QDebug str, const Okular::NormalizedPoint &p)
111 {
112     str.nospace() << "NormPt(" << p.x << "," << p.y << ")";
113     return str.space();
114 }
115 
116 /** class NormalizedRect **/
117 
NormalizedRect()118 NormalizedRect::NormalizedRect()
119     : left(0.0)
120     , top(0.0)
121     , right(0.0)
122     , bottom(0.0)
123 {
124 }
125 
NormalizedRect(double l,double t,double r,double b)126 NormalizedRect::NormalizedRect(double l, double t, double r, double b)
127     // note: check for swapping coords?
128     : left(l)
129     , top(t)
130     , right(r)
131     , bottom(b)
132 {
133 }
134 
NormalizedRect(const QRect & r,double xScale,double yScale)135 NormalizedRect::NormalizedRect(const QRect &r, double xScale, double yScale) // clazy:exclude=function-args-by-value TODO when BIC changes are allowed
136     : left((double)r.left() / xScale)
137     , top((double)r.top() / yScale)
138     , right((double)r.right() / xScale)
139     , bottom((double)r.bottom() / yScale)
140 {
141 }
142 
143 NormalizedRect::NormalizedRect(const NormalizedRect &rect) = default;
144 
fromQRectF(const QRectF & rect)145 NormalizedRect NormalizedRect::fromQRectF(const QRectF &rect)
146 {
147     QRectF nrect = rect.normalized();
148     NormalizedRect ret;
149     ret.left = nrect.left();
150     ret.top = nrect.top();
151     ret.right = nrect.right();
152     ret.bottom = nrect.bottom();
153     return ret;
154 }
155 
isNull() const156 bool NormalizedRect::isNull() const
157 {
158     return left == 0 && top == 0 && right == 0 && bottom == 0;
159 }
160 
contains(double x,double y) const161 bool NormalizedRect::contains(double x, double y) const
162 {
163     return x >= left && x <= right && y >= top && y <= bottom;
164 }
165 
intersects(const NormalizedRect & r) const166 bool NormalizedRect::intersects(const NormalizedRect &r) const
167 {
168     return (r.left <= right) && (r.right >= left) && (r.top <= bottom) && (r.bottom >= top);
169 }
170 
intersects(const NormalizedRect * r) const171 bool NormalizedRect::intersects(const NormalizedRect *r) const
172 {
173     return (r->left <= right) && (r->right >= left) && (r->top <= bottom) && (r->bottom >= top);
174 }
175 
intersects(double l,double t,double r,double b) const176 bool NormalizedRect::intersects(double l, double t, double r, double b) const
177 {
178     return (l <= right) && (r >= left) && (t <= bottom) && (b >= top);
179 }
180 
operator |(const NormalizedRect & r) const181 NormalizedRect NormalizedRect::operator|(const NormalizedRect &r) const
182 {
183     NormalizedRect ret;
184     ret.left = qMin(left, r.left);
185     ret.top = qMin(top, r.top);
186     ret.bottom = qMax(bottom, r.bottom);
187     ret.right = qMax(right, r.right);
188     return ret;
189 }
190 
operator |=(const NormalizedRect & r)191 NormalizedRect &NormalizedRect::operator|=(const NormalizedRect &r)
192 {
193     left = qMin(left, r.left);
194     top = qMin(top, r.top);
195     bottom = qMax(bottom, r.bottom);
196     right = qMax(right, r.right);
197     return *this;
198 }
199 
operator &(const NormalizedRect & r) const200 NormalizedRect NormalizedRect::operator&(const NormalizedRect &r) const
201 {
202     if (isNull() || r.isNull())
203         return NormalizedRect();
204 
205     NormalizedRect ret;
206     ret.left = qMax(left, r.left);
207     ret.top = qMax(top, r.top);
208     ret.bottom = qMin(bottom, r.bottom);
209     ret.right = qMin(right, r.right);
210     return ret;
211 }
212 
213 NormalizedRect &NormalizedRect::operator=(const NormalizedRect &r) = default;
214 
215 NormalizedRect::~NormalizedRect() = default;
216 
operator ==(const NormalizedRect & r) const217 bool NormalizedRect::operator==(const NormalizedRect &r) const
218 {
219     return (isNull() && r.isNull()) || (fabs(left - r.left) < 1e-4 && fabs(right - r.right) < 1e-4 && fabs(top - r.top) < 1e-4 && fabs(bottom - r.bottom) < 1e-4);
220 }
221 
center() const222 NormalizedPoint NormalizedRect::center() const
223 {
224     return NormalizedPoint((left + right) / 2.0, (top + bottom) / 2.0);
225 }
226 
227 /*
228 QDebug operator << (QDebug str , const NormalizedRect &r)
229 {
230     str << "[" <<r.left() << "," << r.top() << "] x "<< "[" <<r.right() << "," << r.bottom() << "]";
231     return str;
232 }*/
233 
geometry(int xScale,int yScale) const234 QRect NormalizedRect::geometry(int xScale, int yScale) const
235 {
236     int l = (int)(left * xScale), t = (int)(top * yScale), r = (int)(right * xScale), b = (int)(bottom * yScale);
237 
238     return QRect(l, t, r - l + 1, b - t + 1);
239 }
240 
roundedGeometry(int xScale,int yScale) const241 QRect NormalizedRect::roundedGeometry(int xScale, int yScale) const
242 {
243     int l = (int)(left * xScale + 0.5), t = (int)(top * yScale + 0.5), r = (int)(right * xScale + 0.5), b = (int)(bottom * yScale + 0.5);
244 
245     return QRect(l, t, r - l + 1, b - t + 1);
246 }
247 
transform(const QTransform & matrix)248 void NormalizedRect::transform(const QTransform &matrix)
249 {
250     QRectF rect(left, top, right - left, bottom - top);
251     rect = matrix.mapRect(rect);
252 
253     left = rect.left();
254     top = rect.top();
255     right = rect.right();
256     bottom = rect.bottom();
257 }
258 
qHash(const NormalizedRect & r,uint seed)259 uint Okular::qHash(const NormalizedRect &r, uint seed)
260 {
261     return ::qHash(r.bottom, ::qHash(r.right, ::qHash(r.top, ::qHash(r.left, seed))));
262 }
263 
operator <<(QDebug str,const Okular::NormalizedRect & r)264 QDebug operator<<(QDebug str, const Okular::NormalizedRect &r)
265 {
266     str.nospace() << "NormRect(" << r.left << "," << r.top << " x " << (r.right - r.left) << "+" << (r.bottom - r.top) << ")";
267     return str.space();
268 }
269 
RegularAreaRect()270 RegularAreaRect::RegularAreaRect()
271     : RegularArea<NormalizedRect, QRect>()
272     , d(nullptr)
273 {
274 }
275 
RegularAreaRect(const RegularAreaRect & rar)276 RegularAreaRect::RegularAreaRect(const RegularAreaRect &rar)
277     : RegularArea<NormalizedRect, QRect>(rar)
278     , d(nullptr)
279 {
280 }
281 
~RegularAreaRect()282 RegularAreaRect::~RegularAreaRect()
283 {
284 }
285 
operator =(const RegularAreaRect & rar)286 RegularAreaRect &RegularAreaRect::operator=(const RegularAreaRect &rar)
287 {
288     if (this != &rar) {
289         RegularArea<NormalizedRect, QRect>::operator=(rar);
290     }
291     return *this;
292 }
293 
HighlightAreaRect(const RegularAreaRect * area)294 HighlightAreaRect::HighlightAreaRect(const RegularAreaRect *area)
295     : RegularAreaRect()
296     , s_id(-1)
297 {
298     if (area) {
299         RegularAreaRect::ConstIterator it = area->begin();
300         RegularAreaRect::ConstIterator itEnd = area->end();
301         for (; it != itEnd; ++it) {
302             append(NormalizedRect(*it));
303         }
304     }
305 }
306 
307 /** class ObjectRect **/
308 
ObjectRect(double l,double t,double r,double b,bool ellipse,ObjectType type,void * object)309 ObjectRect::ObjectRect(double l, double t, double r, double b, bool ellipse, ObjectType type, void *object)
310     : m_objectType(type)
311     , m_object(object)
312 {
313     // assign coordinates swapping them if negative width or height
314     QRectF rect(r > l ? l : r, b > t ? t : b, fabs(r - l), fabs(b - t));
315     if (ellipse)
316         m_path.addEllipse(rect);
317     else
318         m_path.addRect(rect);
319 
320     m_transformedPath = m_path;
321 }
322 
ObjectRect(const NormalizedRect & r,bool ellipse,ObjectType type,void * object)323 ObjectRect::ObjectRect(const NormalizedRect &r, bool ellipse, ObjectType type, void *object)
324     : m_objectType(type)
325     , m_object(object)
326 {
327     QRectF rect(r.left, r.top, fabs(r.right - r.left), fabs(r.bottom - r.top));
328     if (ellipse)
329         m_path.addEllipse(rect);
330     else
331         m_path.addRect(rect);
332 
333     m_transformedPath = m_path;
334 }
335 
ObjectRect(const QPolygonF & poly,ObjectType type,void * object)336 ObjectRect::ObjectRect(const QPolygonF &poly, ObjectType type, void *object)
337     : m_objectType(type)
338     , m_object(object)
339 {
340     m_path.addPolygon(poly);
341 
342     m_transformedPath = m_path;
343 }
344 
objectType() const345 ObjectRect::ObjectType ObjectRect::objectType() const
346 {
347     return m_objectType;
348 }
349 
object() const350 const void *ObjectRect::object() const
351 {
352     return m_object;
353 }
354 
region() const355 const QPainterPath &ObjectRect::region() const
356 {
357     return m_transformedPath;
358 }
359 
boundingRect(double xScale,double yScale) const360 QRect ObjectRect::boundingRect(double xScale, double yScale) const
361 {
362     const QRectF &br = m_transformedPath.boundingRect();
363 
364     return QRect((int)(br.left() * xScale), (int)(br.top() * yScale), (int)(br.width() * xScale), (int)(br.height() * yScale));
365 }
366 
contains(double x,double y,double,double) const367 bool ObjectRect::contains(double x, double y, double, double) const
368 {
369     return m_transformedPath.contains(QPointF(x, y));
370 }
371 
transform(const QTransform & matrix)372 void ObjectRect::transform(const QTransform &matrix)
373 {
374     m_transformedPath = matrix.map(m_path);
375 }
376 
distanceSqr(double x,double y,double xScale,double yScale) const377 double ObjectRect::distanceSqr(double x, double y, double xScale, double yScale) const
378 {
379     switch (m_objectType) {
380     case Action:
381     case Image: {
382         const QRectF &rect(m_transformedPath.boundingRect());
383         return NormalizedRect(rect.x(), rect.y(), rect.right(), rect.bottom()).distanceSqr(x, y, xScale, yScale);
384     }
385     case OAnnotation: {
386         return static_cast<Annotation *>(m_object)->d_func()->distanceSqr(x, y, xScale, yScale);
387     }
388     case SourceRef: {
389         const SourceRefObjectRect *sr = static_cast<const SourceRefObjectRect *>(this);
390         const NormalizedPoint &point = sr->m_point;
391         if (point.x == -1.0) {
392             return pow((y - point.y) * yScale, 2);
393         } else if (point.y == -1.0) {
394             return pow((x - point.x) * xScale, 2);
395         } else {
396             return pow((x - point.x) * xScale, 2) + pow((y - point.y) * yScale, 2);
397         }
398     }
399     }
400     return 0.0;
401 }
402 
~ObjectRect()403 ObjectRect::~ObjectRect()
404 {
405     if (!m_object)
406         return;
407 
408     if (m_objectType == Action)
409         delete static_cast<Okular::Action *>(m_object);
410     else if (m_objectType == SourceRef)
411         delete static_cast<Okular::SourceReference *>(m_object);
412     else
413         qCDebug(OkularCoreDebug).nospace() << "Object deletion not implemented for type '" << m_objectType << "'.";
414 }
415 
416 /** class AnnotationObjectRect **/
417 
AnnotationObjectRect(Annotation * annotation)418 AnnotationObjectRect::AnnotationObjectRect(Annotation *annotation)
419     : ObjectRect(QPolygonF(), OAnnotation, annotation)
420     , m_annotation(annotation)
421 {
422 }
423 
annotation() const424 Annotation *AnnotationObjectRect::annotation() const
425 {
426     return m_annotation;
427 }
428 
boundingRect(double xScale,double yScale) const429 QRect AnnotationObjectRect::boundingRect(double xScale, double yScale) const
430 {
431     const QRect annotRect = AnnotationUtils::annotationGeometry(m_annotation, xScale, yScale);
432     const QPoint center = annotRect.center();
433 
434     // Make sure that the rectangle has a minimum size, so that it's possible
435     // to click on it
436     const int minSize = 14;
437     const QRect minRect(center.x() - minSize / 2, center.y() - minSize / 2, minSize, minSize);
438 
439     return annotRect | minRect;
440 }
441 
contains(double x,double y,double xScale,double yScale) const442 bool AnnotationObjectRect::contains(double x, double y, double xScale, double yScale) const
443 {
444     return boundingRect(xScale, yScale).contains((int)(x * xScale), (int)(y * yScale), false);
445 }
446 
~AnnotationObjectRect()447 AnnotationObjectRect::~AnnotationObjectRect()
448 {
449     // the annotation pointer is kept elsewehere (in Page, most probably),
450     // so just release its pointer
451     m_object = nullptr;
452 }
453 
transform(const QTransform & matrix)454 void AnnotationObjectRect::transform(const QTransform &matrix)
455 {
456     m_annotation->d_func()->annotationTransform(matrix);
457 }
458 
459 /** class SourceRefObjectRect **/
460 
SourceRefObjectRect(const NormalizedPoint & point,void * srcRef)461 SourceRefObjectRect::SourceRefObjectRect(const NormalizedPoint &point, void *srcRef)
462     : ObjectRect(point.x, point.y, .0, .0, false, SourceRef, srcRef)
463     , m_point(point)
464 {
465     const double x = m_point.x < 0.0 ? 0.5 : m_point.x;
466     const double y = m_point.y < 0.0 ? 0.5 : m_point.y;
467     const QRectF rect(x - 2, y - 2, 5, 5);
468     m_path.addRect(rect);
469 
470     m_transformedPath = m_path;
471 }
472 
boundingRect(double xScale,double yScale) const473 QRect SourceRefObjectRect::boundingRect(double xScale, double yScale) const
474 {
475     const double x = m_point.x < 0.0 ? 0.5 : m_point.x;
476     const double y = m_point.y < 0.0 ? 0.5 : m_point.y;
477 
478     return QRect(x * xScale, y * yScale, 1, 1);
479 }
480 
contains(double x,double y,double xScale,double yScale) const481 bool SourceRefObjectRect::contains(double x, double y, double xScale, double yScale) const
482 {
483     return distanceSqr(x, y, xScale, yScale) < (pow(7.0 / xScale, 2) + pow(7.0 / yScale, 2));
484 }
485 
486 /** class NonOwningObjectRect **/
487 
NonOwningObjectRect(double left,double top,double right,double bottom,bool ellipse,ObjectType type,void * object)488 NonOwningObjectRect::NonOwningObjectRect(double left, double top, double right, double bottom, bool ellipse, ObjectType type, void *object)
489     : ObjectRect(left, top, right, bottom, ellipse, type, object)
490 {
491 }
492 
~NonOwningObjectRect()493 NonOwningObjectRect::~NonOwningObjectRect()
494 {
495     // Set m_object so that ~ObjectRect() doesn't delete it
496     m_object = nullptr;
497 }
498