1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QtTest/QtTest>
31
32#include <qfile.h>
33#include <qpainterpath.h>
34#include <qpen.h>
35#include <qmath.h>
36
37class tst_QPainterPath : public QObject
38{
39 Q_OBJECT
40
41public:
42public slots:
43 void cleanupTestCase();
44private slots:
45 void getSetCheck();
46 void clear();
47 void reserveAndCapacity();
48 void swap();
49
50 void contains_QPointF_data();
51 void contains_QPointF();
52
53 void contains_QRectF_data();
54 void contains_QRectF();
55
56 void intersects_QRectF_data();
57 void intersects_QRectF();
58
59 void testContainsAndIntersects_data();
60 void testContainsAndIntersects();
61
62 void testSimplified_data();
63 void testSimplified();
64
65 void testStroker_data();
66 void testStroker();
67
68 void currentPosition();
69
70 void testOperatorEquals();
71 void testOperatorEquals_fuzzy();
72 void testOperatorDatastream();
73
74 void testArcMoveTo_data();
75 void testArcMoveTo();
76 void setElementPositionAt();
77
78 void testOnPath_data();
79 void testOnPath();
80
81 void pointAtPercent_data();
82 void pointAtPercent();
83
84 void angleAtPercent();
85
86 void arcWinding_data();
87 void arcWinding();
88
89 void testToFillPolygons();
90
91#if QT_CONFIG(signaling_nan)
92 void testNaNandInfinites();
93#endif
94
95 void closing();
96
97 void operators_data();
98 void operators();
99
100 void connectPathDuplicatePoint();
101 void connectPathMoveTo();
102
103 void translate();
104
105 void lineWithinBounds();
106
107 void intersectionEquality();
108 void intersectionPointOnEdge();
109};
110
111void tst_QPainterPath::cleanupTestCase()
112{
113 QFile::remove(fileName: QLatin1String("data"));
114}
115
116// Testing get/set functions
117void tst_QPainterPath::getSetCheck()
118{
119 QPainterPathStroker obj1;
120 // qreal QPainterPathStroker::width()
121 // void QPainterPathStroker::setWidth(qreal)
122 obj1.setWidth(0.0);
123 QCOMPARE(qreal(1.0), obj1.width()); // Pathstroker sets with to 1 if <= 0
124 obj1.setWidth(0.5);
125 QCOMPARE(qreal(0.5), obj1.width());
126 obj1.setWidth(1.1);
127 QCOMPARE(qreal(1.1), obj1.width());
128
129 // qreal QPainterPathStroker::miterLimit()
130 // void QPainterPathStroker::setMiterLimit(qreal)
131 obj1.setMiterLimit(0.0);
132 QCOMPARE(qreal(0.0), obj1.miterLimit());
133 obj1.setMiterLimit(1.1);
134 QCOMPARE(qreal(1.1), obj1.miterLimit());
135
136 // qreal QPainterPathStroker::curveThreshold()
137 // void QPainterPathStroker::setCurveThreshold(qreal)
138 obj1.setCurveThreshold(0.0);
139 QCOMPARE(qreal(0.0), obj1.curveThreshold());
140 obj1.setCurveThreshold(1.1);
141 QCOMPARE(qreal(1.1), obj1.curveThreshold());
142}
143
144void tst_QPainterPath::swap()
145{
146 QPainterPath p1;
147 p1.addRect( x: 0, y: 0,w: 10,h: 10);
148 QPainterPath p2;
149 p2.addRect(x: 10,y: 10,w: 10,h: 10);
150 p1.swap(other&: p2);
151 QCOMPARE(p1.boundingRect().toRect(), QRect(10,10,10,10));
152 QCOMPARE(p2.boundingRect().toRect(), QRect( 0, 0,10,10));
153}
154
155void tst_QPainterPath::clear()
156{
157 QPainterPath p1;
158 QPainterPath p2;
159 p1.clear();
160 QCOMPARE(p1, p2);
161
162 p1.addRect(x: 0, y: 0, w: 10, h: 10);
163 p1.clear();
164 QCOMPARE(p1, p2);
165
166 p1.lineTo(x: 50, y: 50);
167 QPainterPath p3;
168 QCOMPARE(p1.elementCount(), 2);
169 p3.lineTo(x: 50, y: 50);
170 QCOMPARE(p1, p3);
171
172 QCOMPARE(p1.fillRule(), Qt::OddEvenFill);
173 p1.setFillRule(Qt::WindingFill);
174 QVERIFY(p1 != p3);
175 p1.clear();
176 QVERIFY(p1 != p3);
177 p1.setFillRule(Qt::OddEvenFill);
178 QCOMPARE(p1, p2);
179
180 QPainterPath p4;
181 QCOMPARE(p4.fillRule(), Qt::OddEvenFill);
182 p4.setFillRule(Qt::WindingFill);
183 QCOMPARE(p4.fillRule(), Qt::WindingFill);
184 p4.clear();
185 QCOMPARE(p4.fillRule(), Qt::WindingFill);
186 p4 = QPainterPath();
187 QCOMPARE(p4.fillRule(), Qt::OddEvenFill);
188}
189
190void tst_QPainterPath::reserveAndCapacity()
191{
192 QPainterPath p;
193 QVERIFY(p.capacity() == 0);
194
195 p.addRect(x: 0, y: 0, w: 10, h: 10);
196 QVERIFY(p.capacity() > 0);
197
198 p.clear();
199 QVERIFY(p.capacity() > 0);
200
201 p = QPainterPath{};
202 QVERIFY(p.capacity() == 0);
203
204 p.moveTo(x: 100, y: 100);
205 QVERIFY(p.capacity() > 1);
206
207 p.reserve(size: 1000);
208 QVERIFY(p.capacity() >= 1000);
209
210 p.reserve(size: 0);
211 QVERIFY(p.capacity() >= 1000);
212
213 QPainterPath p2;
214 p2.reserve(size: 10);
215 QVERIFY(p.capacity() >= 10);
216}
217
218Q_DECLARE_METATYPE(QPainterPath)
219
220void tst_QPainterPath::currentPosition()
221{
222 QPainterPath p;
223
224 QCOMPARE(p.currentPosition(), QPointF());
225
226 p.moveTo(x: 100, y: 100);
227 QCOMPARE(p.currentPosition(), QPointF(100, 100));
228
229 p.lineTo(x: 200, y: 200);
230 QCOMPARE(p.currentPosition(), QPointF(200, 200));
231
232 p.cubicTo(ctrlPt1x: 300, ctrlPt1y: 200, ctrlPt2x: 200, ctrlPt2y: 300, endPtx: 500, endPty: 500);
233 QCOMPARE(p.currentPosition(), QPointF(500, 500));
234}
235
236void tst_QPainterPath::contains_QPointF_data()
237{
238 QTest::addColumn<QPainterPath>(name: "path");
239 QTest::addColumn<QPointF>(name: "pt");
240 QTest::addColumn<bool>(name: "contained");
241
242 QPainterPath path;
243 path.addRect(x: 0, y: 0, w: 100, h: 100);
244
245 // #####
246 // # #
247 // # #
248 // # #
249 // #####
250
251 QTest::newRow(dataTag: "[0,0] in [0,0,100,100]") << path << QPointF(0, 0) << true;
252
253 QTest::newRow(dataTag: "[99,0] in [0,0,100,100]") << path << QPointF(99, 0) << true;
254 QTest::newRow(dataTag: "[0,99] in [0,0,100,100]") << path << QPointF(0, 99) << true;
255 QTest::newRow(dataTag: "[99,99] in [0,0,100,100]") << path << QPointF(99, 99) << true;
256
257 QTest::newRow(dataTag: "[99.99,0] in [0,0,100,100]") << path << QPointF(99.99, 0) << true;
258 QTest::newRow(dataTag: "[0,99.99] in [0,0,100,100]") << path << QPointF(0, 99.99) << true;
259 QTest::newRow(dataTag: "[99.99,99.99] in [0,0,100,100]") << path << QPointF(99.99, 99.99) << true;
260
261 QTest::newRow(dataTag: "[0.01,0.01] in [0,0,100,100]") << path << QPointF(0.01, 0.01) << true;
262 QTest::newRow(dataTag: "[0,0.01] in [0,0,100,100]") << path << QPointF(0, 0.01) << true;
263 QTest::newRow(dataTag: "[0.01,0] in [0,0,100,100]") << path << QPointF(0.01, 0) << true;
264
265 QTest::newRow(dataTag: "[-0.01,-0.01] in [0,0,100,100]") << path << QPointF(-0.01, -0.01) << false;
266 QTest::newRow(dataTag: "[-0,-0.01] in [0,0,100,100]") << path << QPointF(0, -0.01) << false;
267 QTest::newRow(dataTag: "[-0.01,0] in [0,0,100,100]") << path << QPointF(-0.01, 0) << false;
268
269
270 QTest::newRow(dataTag: "[-10,0] in [0,0,100,100]") << path << QPointF(-10, 0) << false;
271 QTest::newRow(dataTag: "[100,0] in [0,0,100,100]") << path << QPointF(100, 0) << false;
272
273 QTest::newRow(dataTag: "[0,-10] in [0,0,100,100]") << path << QPointF(0, -10) << false;
274 QTest::newRow(dataTag: "[0,100] in [0,0,100,100]") << path << QPointF(0, 100) << false;
275
276 QTest::newRow(dataTag: "[100.1,0] in [0,0,100,100]") << path << QPointF(100.1, 0) << false;
277 QTest::newRow(dataTag: "[0,100.1] in [0,0,100,100]") << path << QPointF(0, 100.1) << false;
278
279 path.addRect(x: 50, y: 50, w: 100, h: 100);
280
281 // #####
282 // # #
283 // # #####
284 // # # # #
285 // ##### #
286 // # #
287 // #####
288
289 QTest::newRow(dataTag: "[49,49] in 2 rects") << path << QPointF(49,49) << true;
290 QTest::newRow(dataTag: "[50,50] in 2 rects") << path << QPointF(50,50) << false;
291 QTest::newRow(dataTag: "[100,100] in 2 rects") << path << QPointF(100,100) << true;
292
293 path.setFillRule(Qt::WindingFill);
294 QTest::newRow(dataTag: "[50,50] in 2 rects (winding)") << path << QPointF(50,50) << true;
295
296 path.addEllipse(x: 0, y: 0, w: 150, h: 150);
297
298 // #####
299 // ## ##
300 // # #####
301 // # # # #
302 // ##### #
303 // ## ##
304 // #####
305
306 QTest::newRow(dataTag: "[50,50] in complex (winding)") << path << QPointF(50, 50) << true;
307
308 path.setFillRule(Qt::OddEvenFill);
309 QTest::newRow(dataTag: "[50,50] in complex (windinf)") << path << QPointF(50, 50) << true;
310 QTest::newRow(dataTag: "[49,49] in complex") << path << QPointF(49,49) << false;
311 QTest::newRow(dataTag: "[100,100] in complex") << path << QPointF(49,49) << false;
312
313
314 // unclosed triangle
315 path = QPainterPath();
316 path.moveTo(x: 100, y: 100);
317 path.lineTo(x: 130, y: 70);
318 path.lineTo(x: 150, y: 110);
319
320 QTest::newRow(dataTag: "[100,100] in triangle") << path << QPointF(100, 100) << true;
321 QTest::newRow(dataTag: "[140,100] in triangle") << path << QPointF(140, 100) << true;
322 QTest::newRow(dataTag: "[130,80] in triangle") << path << QPointF(130, 80) << true;
323
324 QTest::newRow(dataTag: "[110,80] in triangle") << path << QPointF(110, 80) << false;
325 QTest::newRow(dataTag: "[150,100] in triangle") << path << QPointF(150, 100) << false;
326 QTest::newRow(dataTag: "[120,110] in triangle") << path << QPointF(120, 110) << false;
327
328 QRectF base_rect(0, 0, 20, 20);
329
330 path = QPainterPath();
331 path.addEllipse(rect: base_rect);
332
333 // not strictly precise, but good enougth to verify fair precision.
334 QPainterPath inside;
335 inside.addEllipse(rect: base_rect.adjusted(xp1: 5, yp1: 5, xp2: -5, yp2: -5));
336 QPolygonF inside_poly = inside.toFillPolygon();
337 for (int i=0; i<inside_poly.size(); ++i)
338 QTest::newRow(dataTag: ("inside_ellipse " + QByteArray::number(i)).constData()) << path << inside_poly.at(i) << true;
339
340 QPainterPath outside;
341 outside.addEllipse(rect: base_rect.adjusted(xp1: -5, yp1: -5, xp2: 5, yp2: 5));
342 QPolygonF outside_poly = outside.toFillPolygon();
343 for (int i=0; i<outside_poly.size(); ++i)
344 QTest::newRow(dataTag: ("outside_ellipse " + QByteArray::number(i)).constData()) << path << outside_poly.at(i) << false;
345
346 path = QPainterPath();
347 base_rect = QRectF(50, 50, 200, 200);
348 path.addEllipse(rect: base_rect);
349 path.setFillRule(Qt::WindingFill);
350
351 QTest::newRow(dataTag: "topleft outside ellipse") << path << base_rect.topLeft() << false;
352 QTest::newRow(dataTag: "topright outside ellipse") << path << base_rect.topRight() << false;
353 QTest::newRow(dataTag: "bottomright outside ellipse") << path << base_rect.bottomRight() << false;
354 QTest::newRow(dataTag: "bottomleft outside ellipse") << path << base_rect.bottomLeft() << false;
355
356 // Test horizontal curve segment
357 path = QPainterPath();
358 path.moveTo(x: 100, y: 100);
359 path.cubicTo(ctrlPt1x: 120, ctrlPt1y: 100, ctrlPt2x: 180, ctrlPt2y: 100, endPtx: 200, endPty: 100);
360 path.lineTo(x: 150, y: 200);
361 path.closeSubpath();
362
363 QTest::newRow(dataTag: "horizontal cubic, out left") << path << QPointF(0, 100) << false;
364 QTest::newRow(dataTag: "horizontal cubic, out right") << path << QPointF(300, 100) <<false;
365 QTest::newRow(dataTag: "horizontal cubic, in mid") << path << QPointF(150, 100) << true;
366
367 path = QPainterPath();
368 path.addEllipse(rect: QRectF(-5000.0, -5000.0, 1500000.0, 1500000.0));
369 QTest::newRow(dataTag: "huge ellipse, qreal=float crash") << path << QPointF(1100000.35, 1098000.2) << true;
370
371}
372
373void tst_QPainterPath::contains_QPointF()
374{
375 QFETCH(QPainterPath, path);
376 QFETCH(QPointF, pt);
377 QFETCH(bool, contained);
378
379 QCOMPARE(path.contains(pt), contained);
380}
381
382void tst_QPainterPath::contains_QRectF_data()
383{
384 QTest::addColumn<QPainterPath>(name: "path");
385 QTest::addColumn<QRectF>(name: "rect");
386 QTest::addColumn<bool>(name: "contained");
387
388 QPainterPath path;
389 path.addRect(x: 0, y: 0, w: 100, h: 100);
390
391 QTest::newRow(dataTag: "same rect") << path << QRectF(0.1, 0.1, 99, 99) << true; // ###
392 QTest::newRow(dataTag: "outside") << path << QRectF(-1, -1, 100, 100) << false;
393 QTest::newRow(dataTag: "covers") << path << QRectF(-1, -1, 102, 102) << false;
394 QTest::newRow(dataTag: "left") << path << QRectF(-10, 50, 5, 5) << false;
395 QTest::newRow(dataTag: "top") << path << QRectF(50, -10, 5, 5) << false;
396 QTest::newRow(dataTag: "right") << path << QRectF(110, 50, 5, 5) << false;
397 QTest::newRow(dataTag: "bottom") << path << QRectF(50, 110, 5, 5) << false;
398
399 path.addRect(x: 50, y: 50, w: 100, h: 100);
400
401 QTest::newRow(dataTag: "r1 top") << path << QRectF(0.1, 0.1, 99, 49) << true;
402 QTest::newRow(dataTag: "r1 left") << path << QRectF(0.1, 0.1, 49, 99) << true;
403 QTest::newRow(dataTag: "r2 right") << path << QRectF(100.01, 50.1, 49, 99) << true;
404 QTest::newRow(dataTag: "r2 bottom") << path << QRectF(50.1, 100.1, 99, 49) << true;
405 QTest::newRow(dataTag: "inside 2 rects") << path << QRectF(51, 51, 48, 48) << false;
406 QTest::newRow(dataTag: "topRight 2 rects") << path << QRectF(100, 0, 49, 49) << false;
407 QTest::newRow(dataTag: "bottomLeft 2 rects") << path << QRectF(0, 100, 49, 49) << false;
408
409 path.setFillRule(Qt::WindingFill);
410 QTest::newRow(dataTag: "inside 2 rects (winding)") << path << QRectF(51, 51, 48, 48) << true;
411
412 path.addEllipse(x: 0, y: 0, w: 150, h: 150);
413 QTest::newRow(dataTag: "topRight 2 rects") << path << QRectF(100, 25, 24, 24) << true;
414 QTest::newRow(dataTag: "bottomLeft 2 rects") << path << QRectF(25, 100, 24, 24) << true;
415
416 path.setFillRule(Qt::OddEvenFill);
417 QTest::newRow(dataTag: "inside 2 rects") << path << QRectF(50, 50, 49, 49) << false;
418}
419
420void tst_QPainterPath::contains_QRectF()
421{
422 QFETCH(QPainterPath, path);
423 QFETCH(QRectF, rect);
424 QFETCH(bool, contained);
425
426 QCOMPARE(path.contains(rect), contained);
427}
428
429static inline QPainterPath rectPath(qreal x, qreal y, qreal w, qreal h)
430{
431 QPainterPath path;
432 path.addRect(x, y, w, h);
433 path.closeSubpath();
434 return path;
435}
436
437static inline QPainterPath ellipsePath(qreal x, qreal y, qreal w, qreal h)
438{
439 QPainterPath path;
440 path.addEllipse(x, y, w, h);
441 path.closeSubpath();
442 return path;
443}
444
445static inline QPainterPath linePath(qreal x1, qreal y1, qreal x2, qreal y2)
446{
447 QPainterPath path;
448 path.moveTo(x: x1, y: y1);
449 path.lineTo(x: x2, y: y2);
450 return path;
451}
452
453void tst_QPainterPath::intersects_QRectF_data()
454{
455 QTest::addColumn<QPainterPath>(name: "path");
456 QTest::addColumn<QRectF>(name: "rect");
457 QTest::addColumn<bool>(name: "intersects");
458
459 QPainterPath path;
460 path.addRect(x: 0, y: 0, w: 100, h: 100);
461
462 QTest::newRow(dataTag: "same rect") << path << QRectF(0.1, 0.1, 99, 99) << true; // ###
463 QTest::newRow(dataTag: "outside") << path << QRectF(-1, -1, 100, 100) << true;
464 QTest::newRow(dataTag: "covers") << path << QRectF(-1, -1, 102, 102) << true;
465 QTest::newRow(dataTag: "left") << path << QRectF(-10, 50, 5, 5) << false;
466 QTest::newRow(dataTag: "top") << path << QRectF(50, -10, 5, 5) << false;
467 QTest::newRow(dataTag: "right") << path << QRectF(110, 50, 5, 5) << false;
468 QTest::newRow(dataTag: "bottom") << path << QRectF(50, 110, 5, 5) << false;
469
470 path.addRect(x: 50, y: 50, w: 100, h: 100);
471
472 QTest::newRow(dataTag: "r1 top") << path << QRectF(0.1, 0.1, 99, 49) << true;
473 QTest::newRow(dataTag: "r1 left") << path << QRectF(0.1, 0.1, 49, 99) << true;
474 QTest::newRow(dataTag: "r2 right") << path << QRectF(100.01, 50.1, 49, 99) << true;
475 QTest::newRow(dataTag: "r2 bottom") << path << QRectF(50.1, 100.1, 99, 49) << true;
476 QTest::newRow(dataTag: "inside 2 rects") << path << QRectF(51, 51, 48, 48) << false;
477
478 path.setFillRule(Qt::WindingFill);
479 QTest::newRow(dataTag: "inside 2 rects (winding)") << path << QRectF(51, 51, 48, 48) << true;
480
481 path.addEllipse(x: 0, y: 0, w: 150, h: 150);
482 QTest::newRow(dataTag: "topRight 2 rects") << path << QRectF(100, 25, 24, 24) << true;
483 QTest::newRow(dataTag: "bottomLeft 2 rects") << path << QRectF(25, 100, 24, 24) << true;
484
485 QTest::newRow(dataTag: "horizontal line") << linePath(x1: 0, y1: 0, x2: 10, y2: 0) << QRectF(1, -1, 2, 2) << true;
486 QTest::newRow(dataTag: "vertical line") << linePath(x1: 0, y1: 0, x2: 0, y2: 10) << QRectF(-1, 1, 2, 2) << true;
487
488 path = QPainterPath();
489 path.addEllipse(rect: QRectF(-5000.0, -5000.0, 1500000.0, 1500000.0));
490 QTest::newRow(dataTag: "huge ellipse, qreal=float crash") << path << QRectF(1100000.35, 1098000.2, 1500000.0, 1500000.0) << true;
491}
492
493void tst_QPainterPath::intersects_QRectF()
494{
495 QFETCH(QPainterPath, path);
496 QFETCH(QRectF, rect);
497 QFETCH(bool, intersects);
498
499 QCOMPARE(path.intersects(rect), intersects);
500}
501
502
503void tst_QPainterPath::testContainsAndIntersects_data()
504{
505 QTest::addColumn<QPainterPath>(name: "path");
506 QTest::addColumn<QPainterPath>(name: "candidate");
507 QTest::addColumn<bool>(name: "contained");
508 QTest::addColumn<bool>(name: "intersects");
509
510 QTest::newRow(dataTag: "rect vs small ellipse (upper left)") << rectPath(x: 0, y: 0, w: 100, h: 100) << ellipsePath(x: 0, y: 0, w: 50, h: 50) << false << true;
511 QTest::newRow(dataTag: "rect vs small ellipse (upper right)") << rectPath(x: 0, y: 0, w: 100, h: 100) << ellipsePath(x: 50, y: 0, w: 50, h: 50) << false << true;
512 QTest::newRow(dataTag: "rect vs small ellipse (lower right)") << rectPath(x: 0, y: 0, w: 100, h: 100) << ellipsePath(x: 50, y: 50, w: 50, h: 50) << false << true;
513 QTest::newRow(dataTag: "rect vs small ellipse (lower left)") << rectPath(x: 0, y: 0, w: 100, h: 100) << ellipsePath(x: 0, y: 50, w: 50, h: 50) << false << true;
514 QTest::newRow(dataTag: "rect vs small ellipse (centered)") << rectPath(x: 0, y: 0, w: 100, h: 100) << ellipsePath(x: 25, y: 25, w: 50, h: 50) << true << true;
515 QTest::newRow(dataTag: "rect vs equal ellipse") << rectPath(x: 0, y: 0, w: 100, h: 100) << ellipsePath(x: 0, y: 0, w: 100, h: 100) << false << true;
516 QTest::newRow(dataTag: "rect vs big ellipse") << rectPath(x: 0, y: 0, w: 100, h: 100) << ellipsePath(x: -10, y: -10, w: 120, h: 120) << false << true;
517
518 QPainterPath twoEllipses = ellipsePath(x: 0, y: 0, w: 100, h: 100).united(r: ellipsePath(x: 200, y: 0, w: 100, h: 100));
519
520 QTest::newRow(dataTag: "rect vs two small ellipses") << rectPath(x: 0, y: 0, w: 100, h: 100) << ellipsePath(x: 25, y: 25, w: 50, h: 50).united(r: ellipsePath(x: 225, y: 25, w: 50, h: 50)) << false << true;
521 QTest::newRow(dataTag: "rect vs two equal ellipses") << rectPath(x: 0, y: 0, w: 100, h: 100) << twoEllipses << false << true;
522
523 QTest::newRow(dataTag: "rect vs self") << rectPath(x: 0, y: 0, w: 100, h: 100) << rectPath(x: 0, y: 0, w: 100, h: 100) << false << true;
524 QTest::newRow(dataTag: "ellipse vs self") << ellipsePath(x: 0, y: 0, w: 100, h: 100) << ellipsePath(x: 0, y: 0, w: 100, h: 100) << false << true;
525
526 QPainterPath twoRects = rectPath(x: 0, y: 0, w: 100, h: 100).united(r: rectPath(x: 200, y: 0, w: 100, h: 100));
527 QTest::newRow(dataTag: "two rects vs small ellipse (upper left)") << twoRects << ellipsePath(x: 0, y: 0, w: 50, h: 50) << false << true;
528 QTest::newRow(dataTag: "two rects vs small ellipse (upper right)") << twoRects << ellipsePath(x: 50, y: 0, w: 50, h: 50) << false << true;
529 QTest::newRow(dataTag: "two rects vs small ellipse (lower right)") << twoRects << ellipsePath(x: 50, y: 50, w: 50, h: 50) << false << true;
530 QTest::newRow(dataTag: "two rects vs small ellipse (lower left)") << twoRects << ellipsePath(x: 0, y: 50, w: 50, h: 50) << false << true;
531 QTest::newRow(dataTag: "two rects vs small ellipse (centered)") << twoRects << ellipsePath(x: 25, y: 25, w: 50, h: 50) << true << true;
532 QTest::newRow(dataTag: "two rects vs equal ellipse") << twoRects << ellipsePath(x: 0, y: 0, w: 100, h: 100) << false << true;
533 QTest::newRow(dataTag: "two rects vs big ellipse") << twoRects << ellipsePath(x: -10, y: -10, w: 120, h: 120) << false << true;
534
535 QTest::newRow(dataTag: "two rects vs two small ellipses") << twoRects << ellipsePath(x: 25, y: 25, w: 50, h: 50).united(r: ellipsePath(x: 225, y: 25, w: 50, h: 50)) << true << true;
536 QTest::newRow(dataTag: "two rects vs two equal ellipses") << twoRects << ellipsePath(x: 0, y: 0, w: 100, h: 100).united(r: ellipsePath(x: 200, y: 0, w: 100, h: 100)) << false << true;
537
538 QTest::newRow(dataTag: "two rects vs self") << twoRects << twoRects << false << true;
539 QTest::newRow(dataTag: "two ellipses vs self") << twoEllipses << twoEllipses << false << true;
540
541 QPainterPath windingRect = rectPath(x: 0, y: 0, w: 100, h: 100);
542 windingRect.addRect(x: 25, y: 25, w: 100, h: 50);
543 windingRect.setFillRule(Qt::WindingFill);
544
545 QTest::newRow(dataTag: "rect with winding rule vs tall rect") << windingRect << rectPath(x: 40, y: 20, w: 20, h: 60) << true << true;
546 QTest::newRow(dataTag: "rect with winding rule vs self") << windingRect << windingRect << false << true;
547
548 QPainterPath thickFrame = rectPath(x: 0, y: 0, w: 100, h: 100).subtracted(r: rectPath(x: 25, y: 25, w: 50, h: 50));
549 QPainterPath thinFrame = rectPath(x: 10, y: 10, w: 80, h: 80).subtracted(r: rectPath(x: 15, y: 15, w: 70, h: 70));
550
551 QTest::newRow(dataTag: "thin frame in thick frame") << thickFrame << thinFrame << true << true;
552 QTest::newRow(dataTag: "rect in thick frame") << thickFrame << rectPath(x: 40, y: 40, w: 20, h: 20) << false << false;
553 QTest::newRow(dataTag: "rect in thin frame") << thinFrame << rectPath(x: 40, y: 40, w: 20, h: 20) << false << false;
554
555 QPainterPath ellipses;
556 ellipses.addEllipse(x: 0, y: 0, w: 10, h: 10);
557 ellipses.addEllipse(x: 4, y: 4, w: 2, h: 2);
558 ellipses.setFillRule(Qt::WindingFill);
559
560 // the definition of QPainterPath::intersects() and contains() is fill-area based,
561 QTest::newRow(dataTag: "line in rect") << rectPath(x: 0, y: 0, w: 100, h: 100) << linePath(x1: 10, y1: 10, x2: 90, y2: 90) << true << true;
562 QTest::newRow(dataTag: "horizontal line in rect") << rectPath(x: 0, y: 0, w: 100, h: 100) << linePath(x1: 10, y1: 50, x2: 90, y2: 50) << true << true;
563 QTest::newRow(dataTag: "vertical line in rect") << rectPath(x: 0, y: 0, w: 100, h: 100) << linePath(x1: 50, y1: 10, x2: 50, y2: 90) << true << true;
564
565 QTest::newRow(dataTag: "line through rect") << rectPath(x: 0, y: 0, w: 100, h: 100) << linePath(x1: -10, y1: -10, x2: 110, y2: 110) << false << true;
566 QTest::newRow(dataTag: "line through rect 2") << rectPath(x: 0, y: 0, w: 100, h: 100) << linePath(x1: -10, y1: 0, x2: 110, y2: 100) << false << true;
567 QTest::newRow(dataTag: "line through rect 3") << rectPath(x: 0, y: 0, w: 100, h: 100) << linePath(x1: 5, y1: 10, x2: 110, y2: 100) << false << true;
568 QTest::newRow(dataTag: "line through rect 4") << rectPath(x: 0, y: 0, w: 100, h: 100) << linePath(x1: -10, y1: 0, x2: 90, y2: 90) << false << true;
569
570 QTest::newRow(dataTag: "horizontal line through rect") << rectPath(x: 0, y: 0, w: 100, h: 100) << linePath(x1: -10, y1: 50, x2: 110, y2: 50) << false << true;
571 QTest::newRow(dataTag: "vertical line through rect") << rectPath(x: 0, y: 0, w: 100, h: 100) << linePath(x1: 50, y1: -10, x2: 50, y2: 110) << false << true;
572
573 QTest::newRow(dataTag: "line vs line") << linePath(x1: 0, y1: 0, x2: 10, y2: 10) << linePath(x1: 10, y1: 0, x2: 0, y2: 10) << false << true;
574
575 QTest::newRow(dataTag: "line in rect with hole") << rectPath(x: 0, y: 0, w: 10, h: 10).subtracted(r: rectPath(x: 2, y: 2, w: 6, h: 6)) << linePath(x1: 4, y1: 4, x2: 6, y2: 6) << false << false;
576 QTest::newRow(dataTag: "line in ellipse") << ellipses << linePath(x1: 3, y1: 5, x2: 7, y2: 5) << false << true;
577 QTest::newRow(dataTag: "line in ellipse 2") << ellipses << linePath(x1: 4.5, y1: 5, x2: 5.5, y2: 5) << true << true;
578
579 QTest::newRow(dataTag: "winding ellipse") << ellipses << ellipsePath(x: 4, y: 4, w: 2, h: 2) << false << true;
580 QTest::newRow(dataTag: "winding ellipse 2") << ellipses << ellipsePath(x: 4.5, y: 4.5, w: 1, h: 1) << true << true;
581 ellipses.setFillRule(Qt::OddEvenFill);
582 QTest::newRow(dataTag: "odd even ellipse") << ellipses << ellipsePath(x: 4, y: 4, w: 2, h: 2) << false << true;
583 QTest::newRow(dataTag: "odd even ellipse 2") << ellipses << ellipsePath(x: 4.5, y: 4.5, w: 1, h: 1) << false << false;
584}
585
586void tst_QPainterPath::testContainsAndIntersects()
587{
588 QFETCH(QPainterPath, path);
589 QFETCH(QPainterPath, candidate);
590 QFETCH(bool, contained);
591 QFETCH(bool, intersects);
592
593 QCOMPARE(path.intersects(candidate), intersects);
594 QCOMPARE(path.contains(candidate), contained);
595}
596
597void tst_QPainterPath::testSimplified_data()
598{
599 QTest::addColumn<QPainterPath>(name: "path");
600 QTest::addColumn<int>(name: "elements");
601
602 QTest::newRow(dataTag: "rect") << rectPath(x: 0, y: 0, w: 10, h: 10) << 5;
603
604 QPainterPath twoRects = rectPath(x: 0, y: 0, w: 10, h: 10);
605 twoRects.addPath(path: rectPath(x: 5, y: 0, w: 10, h: 10));
606 QTest::newRow(dataTag: "two rects (odd)") << twoRects << 10;
607
608 twoRects.setFillRule(Qt::WindingFill);
609 QTest::newRow(dataTag: "two rects (winding)") << twoRects << 5;
610
611 QPainterPath threeSteps = rectPath(x: 0, y: 0, w: 10, h: 10);
612 threeSteps.addPath(path: rectPath(x: 0, y: 10, w: 20, h: 10));
613 threeSteps.addPath(path: rectPath(x: 0, y: 20, w: 30, h: 10));
614
615 QTest::newRow(dataTag: "three rects (steps)") << threeSteps << 9;
616}
617
618void tst_QPainterPath::testSimplified()
619{
620 QFETCH(QPainterPath, path);
621 QFETCH(int, elements);
622
623 QPainterPath simplified = path.simplified();
624
625 QCOMPARE(simplified.elementCount(), elements);
626
627 QVERIFY(simplified.subtracted(path).isEmpty());
628 QVERIFY(path.subtracted(simplified).isEmpty());
629}
630
631void tst_QPainterPath::testStroker_data()
632{
633 QTest::addColumn<QPainterPath>(name: "path");
634 QTest::addColumn<QPen>(name: "pen");
635 QTest::addColumn<QPainterPath>(name: "stroke");
636
637 QTest::newRow(dataTag: "line 1") << linePath(x1: 2, y1: 2, x2: 10, y2: 2) << QPen(Qt::black, 2, Qt::SolidLine, Qt::FlatCap) << rectPath(x: 2, y: 1, w: 8, h: 2);
638 QTest::newRow(dataTag: "line 2") << linePath(x1: 2, y1: 2, x2: 10, y2: 2) << QPen(Qt::black, 2, Qt::SolidLine, Qt::SquareCap) << rectPath(x: 1, y: 1, w: 10, h: 2);
639
640 QTest::newRow(dataTag: "rect") << rectPath(x: 1, y: 1, w: 8, h: 8) << QPen(Qt::black, 2, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin) << rectPath(x: 0, y: 0, w: 10, h: 10).subtracted(r: rectPath(x: 2, y: 2, w: 6, h: 6));
641
642 QTest::newRow(dataTag: "dotted line") << linePath(x1: 0, y1: 0, x2: 10, y2: 0) << QPen(Qt::black, 2, Qt::DotLine) << rectPath(x: -1, y: -1, w: 4, h: 2).united(r: rectPath(x: 5, y: -1, w: 4, h: 2));
643}
644
645void tst_QPainterPath::testStroker()
646{
647 QFETCH(QPainterPath, path);
648 QFETCH(QPen, pen);
649 QFETCH(QPainterPath, stroke);
650
651 QPainterPathStroker stroker;
652 stroker.setWidth(pen.widthF());
653 stroker.setCapStyle(pen.capStyle());
654 stroker.setJoinStyle(pen.joinStyle());
655 stroker.setMiterLimit(pen.miterLimit());
656 stroker.setDashPattern(pen.style());
657 stroker.setDashOffset(pen.dashOffset());
658
659 QPainterPath result = stroker.createStroke(path);
660
661 // check if stroke == result
662 QVERIFY(result.subtracted(stroke).isEmpty());
663 QVERIFY(stroke.subtracted(result).isEmpty());
664}
665
666void tst_QPainterPath::testOperatorEquals()
667{
668 QPainterPath empty1;
669 QPainterPath empty2;
670 QCOMPARE(empty1, empty2);
671
672 QPainterPath rect1;
673 rect1.addRect(x: 100, y: 100, w: 100, h: 100);
674 QCOMPARE(rect1, rect1);
675 QVERIFY(rect1 != empty1);
676
677 QPainterPath rect2;
678 rect2.addRect(x: 100, y: 100, w: 100, h: 100);
679 QCOMPARE(rect1, rect2);
680
681 rect2.setFillRule(Qt::WindingFill);
682 QVERIFY(rect1 != rect2);
683
684 QPainterPath ellipse1;
685 ellipse1.addEllipse(x: 50, y: 50, w: 100, h: 100);
686 QVERIFY(rect1 != ellipse1);
687
688 QPainterPath ellipse2;
689 ellipse2.addEllipse(x: 50, y: 50, w: 100, h: 100);
690 QCOMPARE(ellipse1, ellipse2);
691}
692
693void tst_QPainterPath::testOperatorEquals_fuzzy()
694{
695 // if operator== returns true for two paths it should
696 // also return true when the same transform is applied to both paths
697 {
698 QRectF a(100, 100, 100, 50);
699 QRectF b = a.translated(dx: 1e-14, dy: 1e-14);
700
701 QPainterPath pa;
702 pa.addRect(rect: a);
703 QPainterPath pb;
704 pb.addRect(rect: b);
705
706 QCOMPARE(pa, pb);
707
708 QTransform transform;
709 transform.translate(dx: -100, dy: -100);
710
711 QCOMPARE(transform.map(pa), transform.map(pb));
712 }
713
714 // higher tolerance for error when path's bounding rect is big
715 {
716 QRectF a(1, 1, 1e6, 0.5e6);
717 QRectF b = a.translated(dx: 1e-7, dy: 1e-7);
718
719 QPainterPath pa;
720 pa.addRect(rect: a);
721 QPainterPath pb;
722 pb.addRect(rect: b);
723
724 QCOMPARE(pa, pb);
725
726 QTransform transform;
727 transform.translate(dx: -1, dy: -1);
728
729 QCOMPARE(transform.map(pa), transform.map(pb));
730 }
731
732 // operator== should return true for a path that has
733 // been transformed and then inverse transformed
734 {
735 QPainterPath a;
736 a.addRect(x: 0, y: 0, w: 100, h: 100);
737
738 QTransform transform;
739 transform.translate(dx: 100, dy: 20);
740 transform.scale(sx: 1.5, sy: 1.5);
741
742 QPainterPath b = transform.inverted().map(p: transform.map(p: a));
743
744 QCOMPARE(a, b);
745 }
746
747 {
748 QPainterPath a;
749 a.lineTo(x: 10, y: 0);
750 a.lineTo(x: 10, y: 10);
751 a.lineTo(x: 0, y: 10);
752
753 QPainterPath b;
754 b.lineTo(x: 10, y: 0);
755 b.moveTo(x: 10, y: 10);
756 b.lineTo(x: 0, y: 10);
757
758 QVERIFY(a != b);
759 }
760}
761
762void tst_QPainterPath::testOperatorDatastream()
763{
764 QPainterPath path;
765 path.addEllipse(x: 0, y: 0, w: 100, h: 100);
766 path.addRect(x: 0, y: 0, w: 100, h: 100);
767 path.setFillRule(Qt::WindingFill);
768
769 QTemporaryDir tempDir(QDir::tempPath() + "/tst_qpainterpath.XXXXXX");
770 QVERIFY2(tempDir.isValid(), qPrintable(tempDir.errorString()));
771 // Write out
772 {
773 QFile data(tempDir.path() + "/data");
774 bool ok = data.open(flags: QFile::WriteOnly);
775 QVERIFY(ok);
776 QDataStream stream(&data);
777 stream << path;
778 }
779
780 QPainterPath other;
781 // Read in
782 {
783 QFile data(tempDir.path() + "/data");
784 bool ok = data.open(flags: QFile::ReadOnly);
785 QVERIFY(ok);
786 QDataStream stream(&data);
787 stream >> other;
788 }
789
790 QCOMPARE(other, path);
791}
792
793void tst_QPainterPath::closing()
794{
795 // lineto's
796 {
797 QPainterPath triangle(QPoint(100, 100));
798
799 triangle.lineTo(x: 200, y: 100);
800 triangle.lineTo(x: 200, y: 200);
801 QCOMPARE(triangle.elementCount(), 3);
802
803 //add this line to make sure closeSubpath() also calls detach() and detached properly
804 QPainterPath copied = triangle;
805 triangle.closeSubpath();
806 QCOMPARE(copied.elementCount(), 3);
807
808 QCOMPARE(triangle.elementCount(), 4);
809 QCOMPARE(triangle.elementAt(3).type, QPainterPath::LineToElement);
810
811 triangle.moveTo(x: 300, y: 300);
812 QCOMPARE(triangle.elementCount(), 5);
813 QCOMPARE(triangle.elementAt(4).type, QPainterPath::MoveToElement);
814
815 triangle.lineTo(x: 400, y: 300);
816 triangle.lineTo(x: 400, y: 400);
817 QCOMPARE(triangle.elementCount(), 7);
818
819 triangle.closeSubpath();
820 QCOMPARE(triangle.elementCount(), 8);
821
822 // this will should trigger implicit moveto...
823 triangle.lineTo(x: 600, y: 300);
824 QCOMPARE(triangle.elementCount(), 10);
825 QCOMPARE(triangle.elementAt(8).type, QPainterPath::MoveToElement);
826 QCOMPARE(triangle.elementAt(9).type, QPainterPath::LineToElement);
827
828 triangle.lineTo(x: 600, y: 700);
829 QCOMPARE(triangle.elementCount(), 11);
830 }
831
832 // curveto's
833 {
834 QPainterPath curves(QPoint(100, 100));
835
836 curves.cubicTo(ctrlPt1x: 200, ctrlPt1y: 100, ctrlPt2x: 100, ctrlPt2y: 200, endPtx: 200, endPty: 200);
837 QCOMPARE(curves.elementCount(), 4);
838
839 curves.closeSubpath();
840 QCOMPARE(curves.elementCount(), 5);
841 QCOMPARE(curves.elementAt(4).type, QPainterPath::LineToElement);
842
843 curves.moveTo(x: 300, y: 300);
844 QCOMPARE(curves.elementCount(), 6);
845 QCOMPARE(curves.elementAt(5).type, QPainterPath::MoveToElement);
846
847 curves.cubicTo(ctrlPt1x: 400, ctrlPt1y: 300, ctrlPt2x: 300, ctrlPt2y: 400, endPtx: 400, endPty: 400);
848 QCOMPARE(curves.elementCount(), 9);
849
850 curves.closeSubpath();
851 QCOMPARE(curves.elementCount(), 10);
852
853 // should trigger implicit moveto..
854 curves.cubicTo(ctrlPt1x: 100, ctrlPt1y: 800, ctrlPt2x: 800, ctrlPt2y: 100, endPtx: 800, endPty: 800);
855 QCOMPARE(curves.elementCount(), 14);
856 QCOMPARE(curves.elementAt(10).type, QPainterPath::MoveToElement);
857 QCOMPARE(curves.elementAt(11).type, QPainterPath::CurveToElement);
858 }
859
860 {
861 QPainterPath rects;
862 rects.addRect(x: 100, y: 100, w: 100, h: 100);
863
864 QCOMPARE(rects.elementCount(), 5);
865 QCOMPARE(rects.elementAt(0).type, QPainterPath::MoveToElement);
866 QCOMPARE(rects.elementAt(4).type, QPainterPath::LineToElement);
867
868 rects.addRect(x: 300, y: 100, w: 100,h: 100);
869 QCOMPARE(rects.elementCount(), 10);
870 QCOMPARE(rects.elementAt(5).type, QPainterPath::MoveToElement);
871 QCOMPARE(rects.elementAt(9).type, QPainterPath::LineToElement);
872
873 rects.lineTo(x: 0, y: 0);
874 QCOMPARE(rects.elementCount(), 12);
875 QCOMPARE(rects.elementAt(10).type, QPainterPath::MoveToElement);
876 QCOMPARE(rects.elementAt(11).type, QPainterPath::LineToElement);
877 }
878
879 {
880 QPainterPath ellipses;
881 ellipses.addEllipse(x: 100, y: 100, w: 100, h: 100);
882
883 QCOMPARE(ellipses.elementCount(), 13);
884 QCOMPARE(ellipses.elementAt(0).type, QPainterPath::MoveToElement);
885 QCOMPARE(ellipses.elementAt(10).type, QPainterPath::CurveToElement);
886
887 ellipses.addEllipse(x: 300, y: 100, w: 100,h: 100);
888 QCOMPARE(ellipses.elementCount(), 26);
889 QCOMPARE(ellipses.elementAt(13).type, QPainterPath::MoveToElement);
890 QCOMPARE(ellipses.elementAt(23).type, QPainterPath::CurveToElement);
891
892 ellipses.lineTo(x: 0, y: 0);
893 QCOMPARE(ellipses.elementCount(), 28);
894 QCOMPARE(ellipses.elementAt(26).type, QPainterPath::MoveToElement);
895 QCOMPARE(ellipses.elementAt(27).type, QPainterPath::LineToElement);
896 }
897
898 {
899 QPainterPath path;
900 path.moveTo(x: 10, y: 10);
901 path.lineTo(x: 40, y: 10);
902 path.lineTo(x: 25, y: 20);
903 path.lineTo(x: 10 + 1e-13, y: 10 + 1e-13);
904 QCOMPARE(path.elementCount(), 4);
905 path.closeSubpath();
906 QCOMPARE(path.elementCount(), 4);
907 }
908}
909
910void tst_QPainterPath::testArcMoveTo_data()
911{
912 QTest::addColumn<QRectF>(name: "rect");
913 QTest::addColumn<qreal>(name: "angle");
914
915 static Q_CONSTEXPR QRectF rects[] = {
916 QRectF(100, 100, 100, 100),
917 QRectF(100, 100, -100, 100),
918 QRectF(100, 100, 100, -100),
919 QRectF(100, 100, -100, -100),
920 };
921
922 for (uint domain = 0; domain < sizeof rects / sizeof *rects; ++domain) {
923 const QByteArray dB = QByteArray::number(domain);
924 for (int i=-360; i<=360; ++i) {
925 QTest::newRow(dataTag: ("test " + dB + ' ' + QByteArray::number(i)).constData())
926 << rects[domain] << (qreal) i;
927 }
928
929 // test low angles
930 QTest::newRow(dataTag: "low angles 1") << rects[domain] << (qreal) 1e-10;
931 QTest::newRow(dataTag: "low angles 2") << rects[domain] << (qreal)-1e-10;
932 }
933}
934
935void tst_QPainterPath::operators_data()
936{
937 QTest::addColumn<QPainterPath>(name: "test");
938 QTest::addColumn<QPainterPath>(name: "expected");
939
940 QPainterPath a;
941 QPainterPath b;
942 a.addRect(x: 0, y: 0, w: 100, h: 100);
943 b.addRect(x: 50, y: 50, w: 100, h: 100);
944
945 QTest::newRow(dataTag: "a & b") << (a & b) << a.intersected(r: b);
946 QTest::newRow(dataTag: "a | b") << (a | b) << a.united(r: b);
947 QTest::newRow(dataTag: "a + b") << (a + b) << a.united(r: b);
948 QTest::newRow(dataTag: "a - b") << (a - b) << a.subtracted(r: b);
949
950 QPainterPath c = a;
951 QTest::newRow(dataTag: "a &= b") << (a &= b) << a.intersected(r: b);
952 c = a;
953 QTest::newRow(dataTag: "a |= b") << (a |= b) << a.united(r: b);
954 c = a;
955 QTest::newRow(dataTag: "a += b") << (a += b) << a.united(r: b);
956 c = a;
957 QTest::newRow(dataTag: "a -= b") << (a -= b) << a.subtracted(r: b);
958}
959
960void tst_QPainterPath::operators()
961{
962 QFETCH(QPainterPath, test);
963 QFETCH(QPainterPath, expected);
964
965 QCOMPARE(test, expected);
966}
967
968static inline bool pathFuzzyCompare(double p1, double p2)
969{
970 return qAbs(t: p1 - p2) < 0.001;
971}
972
973
974static inline bool pathFuzzyCompare(float p1, float p2)
975{
976 return qAbs(t: p1 - p2) < 0.001;
977}
978
979
980void tst_QPainterPath::testArcMoveTo()
981{
982 QFETCH(QRectF, rect);
983 QFETCH(qreal, angle);
984
985 QPainterPath path;
986 path.arcMoveTo(rect, angle);
987 path.arcTo(rect, startAngle: angle, arcLength: 30);
988 path.arcTo(rect, startAngle: angle + 30, arcLength: 30);
989
990 QPointF pos = path.elementAt(i: 0);
991
992 QVERIFY((path.elementCount()-1) % 3 == 0);
993
994 qreal x_radius = rect.width() / 2.0;
995 qreal y_radius = rect.height() / 2.0;
996
997 QPointF shouldBe = rect.center()
998 + QPointF(x_radius * qCos(v: qDegreesToRadians(degrees: angle)), -y_radius * qSin(v: qDegreesToRadians(degrees: angle)));
999
1000 qreal iw = 1 / rect.width();
1001 qreal ih = 1 / rect.height();
1002
1003 QVERIFY(pathFuzzyCompare(pos.x() * iw, shouldBe.x() * iw));
1004 QVERIFY(pathFuzzyCompare(pos.y() * ih, shouldBe.y() * ih));
1005}
1006
1007void tst_QPainterPath::testOnPath_data()
1008{
1009 QTest::addColumn<QPainterPath>(name: "path");
1010 QTest::addColumn<qreal>(name: "start");
1011 QTest::addColumn<qreal>(name: "middle");
1012 QTest::addColumn<qreal>(name: "end");
1013
1014 QPainterPath path = QPainterPath(QPointF(153, 199));
1015 path.cubicTo(ctrlPt1: QPointF(147, 61), ctrlPt2: QPointF(414, 18),
1016 endPt: QPointF(355, 201));
1017
1018 QTest::newRow(dataTag: "First case") << path
1019 << qreal(93.0)
1020 << qreal(4.0)
1021 << qreal(252.13);
1022
1023 path = QPainterPath(QPointF(328, 197));
1024 path.cubicTo(ctrlPt1: QPointF(150, 50), ctrlPt2: QPointF(401, 50),
1025 endPt: QPointF(225, 197));
1026 QTest::newRow(dataTag: "Second case") << path
1027 << qreal(140.0)
1028 << qreal(0.0)
1029 << qreal(220.0);
1030
1031 path = QPainterPath(QPointF(328, 197));
1032 path.cubicTo(ctrlPt1: QPointF(101 , 153), ctrlPt2: QPointF(596, 151),
1033 endPt: QPointF(353, 197));
1034 QTest::newRow(dataTag: "Third case") << path
1035 << qreal(169.0)
1036 << qreal(0.22)
1037 << qreal(191.0);
1038
1039 path = QPainterPath(QPointF(153, 199));
1040 path.cubicTo(ctrlPt1: QPointF(59, 53), ctrlPt2: QPointF(597, 218),
1041 endPt: QPointF(355, 201));
1042 QTest::newRow(dataTag: "Fourth case") << path
1043 << qreal(122.0)
1044 << qreal(348.0)
1045 << qreal(175.0);
1046
1047}
1048
1049#define SIGN(x) ((x < 0)?-1:1)
1050void tst_QPainterPath::testOnPath()
1051{
1052 QFETCH(QPainterPath, path);
1053 QFETCH(qreal, start);
1054 QFETCH(qreal, middle);
1055 QFETCH(qreal, end);
1056
1057 int signStart = SIGN(start);
1058 int signMid = SIGN(middle);
1059 int signEnd = SIGN(end);
1060
1061 static const qreal diff = 3;
1062
1063 qreal angle = path.angleAtPercent(t: 0);
1064 QCOMPARE(SIGN(angle), signStart);
1065 QVERIFY(qAbs(angle-start) < diff);
1066
1067 angle = path.angleAtPercent(t: 0.5);
1068 QCOMPARE(SIGN(angle), signMid);
1069 QVERIFY(qAbs(angle-middle) < diff);
1070
1071 angle = path.angleAtPercent(t: 1);
1072 QCOMPARE(SIGN(angle), signEnd);
1073 QVERIFY(qAbs(angle-end) < diff);
1074}
1075
1076void tst_QPainterPath::pointAtPercent_data()
1077{
1078 QTest::addColumn<QPainterPath>(name: "path");
1079 QTest::addColumn<qreal>(name: "percent");
1080 QTest::addColumn<QPointF>(name: "point");
1081
1082 QPainterPath path;
1083 path.lineTo(x: 100, y: 0);
1084
1085 QTest::newRow(dataTag: "Case 1") << path << qreal(0.2) << QPointF(20, 0);
1086 QTest::newRow(dataTag: "Case 2") << path << qreal(0.5) << QPointF(50, 0);
1087 QTest::newRow(dataTag: "Case 3") << path << qreal(0.0) << QPointF(0, 0);
1088 QTest::newRow(dataTag: "Case 4") << path << qreal(1.0) << QPointF(100, 0);
1089
1090 path = QPainterPath();
1091 path.lineTo(x: 0, y: 100);
1092
1093 QTest::newRow(dataTag: "Case 5") << path << qreal(0.2) << QPointF(0, 20);
1094 QTest::newRow(dataTag: "Case 6") << path << qreal(0.5) << QPointF(0, 50);
1095 QTest::newRow(dataTag: "Case 7") << path << qreal(0.0) << QPointF(0, 0);
1096 QTest::newRow(dataTag: "Case 8") << path << qreal(1.0) << QPointF(0, 100);
1097
1098 path.lineTo(x: 300, y: 100);
1099
1100 QTest::newRow(dataTag: "Case 9") << path << qreal(0.25) << QPointF(0, 100);
1101 QTest::newRow(dataTag: "Case 10") << path << qreal(0.5) << QPointF(100, 100);
1102 QTest::newRow(dataTag: "Case 11") << path << qreal(0.75) << QPointF(200, 100);
1103
1104 path = QPainterPath();
1105 path.addEllipse(x: 0, y: 0, w: 100, h: 100);
1106
1107 QTest::newRow(dataTag: "Case 12") << path << qreal(0.0) << QPointF(100, 50);
1108 QTest::newRow(dataTag: "Case 13") << path << qreal(0.25) << QPointF(50, 100);
1109 QTest::newRow(dataTag: "Case 14") << path << qreal(0.5) << QPointF(0, 50);
1110 QTest::newRow(dataTag: "Case 15") << path << qreal(0.75) << QPointF(50, 0);
1111 QTest::newRow(dataTag: "Case 16") << path << qreal(1.0) << QPointF(100, 50);
1112
1113 path = QPainterPath();
1114 QRectF rect(241, 273, 185, 228);
1115 path.addEllipse(rect);
1116 QTest::newRow(dataTag: "Case 17") << path << qreal(1.0) << QPointF(rect.right(), qreal(0.5) * (rect.top() + rect.bottom()));
1117
1118 path = QPainterPath();
1119 path.moveTo(x: 100, y: 100);
1120 QTest::newRow(dataTag: "Case 18") << path << qreal(0.0) << QPointF(100, 100);
1121 QTest::newRow(dataTag: "Case 19") << path << qreal(1.0) << QPointF(100, 100);
1122}
1123
1124void tst_QPainterPath::pointAtPercent()
1125{
1126 QFETCH(QPainterPath, path);
1127 QFETCH(qreal, percent);
1128 QFETCH(QPointF, point);
1129
1130 QPointF result = path.pointAtPercent(t: percent);
1131 QVERIFY(pathFuzzyCompare(point.x() , result.x()));
1132 QVERIFY(pathFuzzyCompare(point.y() , result.y()));
1133}
1134
1135void tst_QPainterPath::setElementPositionAt()
1136{
1137 QPainterPath path(QPointF(42., 42.));
1138 QCOMPARE(path.elementCount(), 1);
1139 QCOMPARE(path.elementAt(0).type, QPainterPath::MoveToElement);
1140 QCOMPARE(path.elementAt(0).x, qreal(42.));
1141 QCOMPARE(path.elementAt(0).y, qreal(42.));
1142
1143 QPainterPath copy = path;
1144 copy.setElementPositionAt(i: 0, x: qreal(0), y: qreal(0));
1145 QCOMPARE(copy.elementCount(), 1);
1146 QCOMPARE(copy.elementAt(0).type, QPainterPath::MoveToElement);
1147 QCOMPARE(copy.elementAt(0).x, qreal(0));
1148 QCOMPARE(copy.elementAt(0).y, qreal(0));
1149
1150 QCOMPARE(path.elementCount(), 1);
1151 QCOMPARE(path.elementAt(0).type, QPainterPath::MoveToElement);
1152 QCOMPARE(path.elementAt(0).x, qreal(42.));
1153 QCOMPARE(path.elementAt(0).y, qreal(42.));
1154}
1155
1156void tst_QPainterPath::angleAtPercent()
1157{
1158 for (int angle = 0; angle < 360; ++angle) {
1159 QLineF line = QLineF::fromPolar(length: 100, angle);
1160 QPainterPath path;
1161 path.moveTo(p: line.p1());
1162 path.lineTo(p: line.p2());
1163
1164 QCOMPARE(path.angleAtPercent(0.5), line.angle());
1165 }
1166}
1167
1168void tst_QPainterPath::arcWinding_data()
1169{
1170 QTest::addColumn<QPainterPath>(name: "path");
1171 QTest::addColumn<QPointF>(name: "point");
1172 QTest::addColumn<bool>(name: "inside");
1173
1174 QPainterPath a;
1175 a.addEllipse(x: 0, y: 0, w: 100, h: 100);
1176 a.addRect(x: 50, y: 50, w: 100, h: 100);
1177
1178 QTest::newRow(dataTag: "Case A (oddeven)") << a << QPointF(55, 55) << false;
1179 a.setFillRule(Qt::WindingFill);
1180 QTest::newRow(dataTag: "Case A (winding)") << a << QPointF(55, 55) << true;
1181
1182 QPainterPath b;
1183 b.arcMoveTo(x: 0, y: 0, w: 100, h: 100, angle: 10);
1184 b.arcTo(x: 0, y: 0, w: 100, h: 100, startAngle: 10, arcLength: 360);
1185 b.addRect(x: 50, y: 50, w: 100, h: 100);
1186
1187 QTest::newRow(dataTag: "Case B (oddeven)") << b << QPointF(55, 55) << false;
1188 b.setFillRule(Qt::WindingFill);
1189 QTest::newRow(dataTag: "Case B (winding)") << b << QPointF(55, 55) << false;
1190
1191 QPainterPath c;
1192 c.arcMoveTo(x: 0, y: 0, w: 100, h: 100, angle: 0);
1193 c.arcTo(x: 0, y: 0, w: 100, h: 100, startAngle: 0, arcLength: 360);
1194 c.addRect(x: 50, y: 50, w: 100, h: 100);
1195
1196 QTest::newRow(dataTag: "Case C (oddeven)") << c << QPointF(55, 55) << false;
1197 c.setFillRule(Qt::WindingFill);
1198 QTest::newRow(dataTag: "Case C (winding)") << c << QPointF(55, 55) << false;
1199
1200 QPainterPath d;
1201 d.arcMoveTo(x: 0, y: 0, w: 100, h: 100, angle: 10);
1202 d.arcTo(x: 0, y: 0, w: 100, h: 100, startAngle: 10, arcLength: -360);
1203 d.addRect(x: 50, y: 50, w: 100, h: 100);
1204
1205 QTest::newRow(dataTag: "Case D (oddeven)") << d << QPointF(55, 55) << false;
1206 d.setFillRule(Qt::WindingFill);
1207 QTest::newRow(dataTag: "Case D (winding)") << d << QPointF(55, 55) << true;
1208
1209 QPainterPath e;
1210 e.arcMoveTo(x: 0, y: 0, w: 100, h: 100, angle: 0);
1211 e.arcTo(x: 0, y: 0, w: 100, h: 100, startAngle: 0, arcLength: -360);
1212 e.addRect(x: 50, y: 50, w: 100, h: 100);
1213
1214 QTest::newRow(dataTag: "Case E (oddeven)") << e << QPointF(55, 55) << false;
1215 e.setFillRule(Qt::WindingFill);
1216 QTest::newRow(dataTag: "Case E (winding)") << e << QPointF(55, 55) << true;
1217}
1218
1219void tst_QPainterPath::arcWinding()
1220{
1221 QFETCH(QPainterPath, path);
1222 QFETCH(QPointF, point);
1223 QFETCH(bool, inside);
1224
1225 QCOMPARE(path.contains(point), inside);
1226}
1227
1228void tst_QPainterPath::testToFillPolygons()
1229{
1230 QPainterPath path;
1231 path.lineTo(p: QPointF(0, 50));
1232 path.lineTo(p: QPointF(50, 50));
1233
1234 path.moveTo(p: QPointF(70, 50));
1235 path.lineTo(p: QPointF(70, 100));
1236 path.lineTo(p: QPointF(40, 100));
1237
1238 const QList<QPolygonF> polygons = path.toFillPolygons();
1239 QCOMPARE(polygons.size(), 2);
1240 QCOMPARE(polygons.first().count(QPointF(70, 50)), 0);
1241}
1242
1243#if QT_CONFIG(signaling_nan)
1244void tst_QPainterPath::testNaNandInfinites()
1245{
1246 QPainterPath path1;
1247 QPainterPath path2 = path1;
1248
1249 QPointF p1 = QPointF(qSNaN(), 1);
1250 QPointF p2 = QPointF(qQNaN(), 1);
1251 QPointF p3 = QPointF(qQNaN(), 1);
1252 QPointF pInf = QPointF(qInf(), 1);
1253
1254 // all these operations with NaN/Inf should be ignored
1255 // can't test operator>> reliably, as we can't create a path with NaN to << later
1256
1257 path1.moveTo(p: p1);
1258 path1.moveTo(x: qSNaN(), y: qQNaN());
1259 path1.moveTo(p: pInf);
1260
1261 path1.lineTo(p: p1);
1262 path1.lineTo(x: qSNaN(), y: qQNaN());
1263 path1.lineTo(p: pInf);
1264
1265 path1.cubicTo(ctrlPt1: p1, ctrlPt2: p2, endPt: p3);
1266 path1.cubicTo(ctrlPt1: p1, ctrlPt2: QPointF(1, 1), endPt: QPointF(2, 2));
1267 path1.cubicTo(ctrlPt1: pInf, ctrlPt2: QPointF(10, 10), endPt: QPointF(5, 1));
1268
1269 path1.quadTo(ctrlPt: p1, endPt: p2);
1270 path1.quadTo(ctrlPt: QPointF(1, 1), endPt: p3);
1271 path1.quadTo(ctrlPt: QPointF(1, 1), endPt: pInf);
1272
1273 path1.arcTo(rect: QRectF(p1, p2), startAngle: 5, arcLength: 5);
1274 path1.arcTo(rect: QRectF(pInf, QPointF(1, 1)), startAngle: 5, arcLength: 5);
1275
1276 path1.addRect(rect: QRectF(p1, p2));
1277 path1.addRect(rect: QRectF(pInf, QPointF(1, 1)));
1278
1279 path1.addEllipse(rect: QRectF(p1, p2));
1280 path1.addEllipse(rect: QRectF(pInf, QPointF(1, 1)));
1281
1282 QCOMPARE(path1, path2);
1283
1284 path1.lineTo(p: QPointF(1, 1));
1285 QVERIFY(path1 != path2);
1286}
1287#endif // signaling_nan
1288
1289void tst_QPainterPath::connectPathDuplicatePoint()
1290{
1291 QPainterPath a;
1292 a.moveTo(x: 10, y: 10);
1293 a.lineTo(x: 20, y: 20);
1294
1295 QPainterPath b;
1296 b.moveTo(x: 20, y: 20);
1297 b.lineTo(x: 30, y: 10);
1298
1299 a.connectPath(path: b);
1300
1301 QPainterPath c;
1302 c.moveTo(x: 10, y: 10);
1303 c.lineTo(x: 20, y: 20);
1304 c.lineTo(x: 30, y: 10);
1305
1306 QCOMPARE(c, a);
1307}
1308
1309void tst_QPainterPath::connectPathMoveTo()
1310{
1311 QPainterPath path1;
1312 QPainterPath path2;
1313 QPainterPath path3;
1314 QPainterPath path4;
1315
1316 path1.moveTo(x: 1,y: 1);
1317
1318 path2.moveTo(x: 4,y: 4);
1319 path2.lineTo(x: 5,y: 6);
1320 path2.lineTo(x: 6,y: 7);
1321
1322 path3.connectPath(path: path2);
1323
1324 path4.lineTo(x: 5,y: 5);
1325
1326 path1.connectPath(path: path2);
1327
1328 QCOMPARE(path1.elementAt(0).type, QPainterPath::MoveToElement);
1329 QCOMPARE(path2.elementAt(0).type, QPainterPath::MoveToElement);
1330 QCOMPARE(path3.elementAt(0).type, QPainterPath::MoveToElement);
1331 QCOMPARE(path4.elementAt(0).type, QPainterPath::MoveToElement);
1332}
1333
1334void tst_QPainterPath::translate()
1335{
1336 QPainterPath path;
1337
1338 // Path with no elements.
1339 QCOMPARE(path.currentPosition(), QPointF());
1340 path.translate(dx: 50.5, dy: 50.5);
1341 QCOMPARE(path.currentPosition(), QPointF());
1342 QCOMPARE(path.translated(50.5, 50.5).currentPosition(), QPointF());
1343
1344 // path.isEmpty(), but we have one MoveTo element that should be translated.
1345 path.moveTo(x: 50, y: 50);
1346 QCOMPARE(path.currentPosition(), QPointF(50, 50));
1347 path.translate(dx: 99.9, dy: 99.9);
1348 QCOMPARE(path.currentPosition(), QPointF(149.9, 149.9));
1349 path.translate(dx: -99.9, dy: -99.9);
1350 QCOMPARE(path.currentPosition(), QPointF(50, 50));
1351 QCOMPARE(path.translated(-50, -50).currentPosition(), QPointF(0, 0));
1352
1353 // Complex path.
1354 QRegion shape(100, 100, 300, 200, QRegion::Ellipse);
1355 shape -= QRect(225, 175, 50, 50);
1356 QPainterPath complexPath;
1357 complexPath.addRegion(region: shape);
1358 QVector<QPointF> untranslatedElements;
1359 for (int i = 0; i < complexPath.elementCount(); ++i)
1360 untranslatedElements.append(t: QPointF(complexPath.elementAt(i)));
1361
1362 const QPainterPath untranslatedComplexPath(complexPath);
1363 const QPointF offset(100, 100);
1364 complexPath.translate(offset);
1365
1366 for (int i = 0; i < complexPath.elementCount(); ++i)
1367 QCOMPARE(QPointF(complexPath.elementAt(i)) - offset, untranslatedElements.at(i));
1368
1369 QCOMPARE(complexPath.translated(-offset), untranslatedComplexPath);
1370}
1371
1372
1373void tst_QPainterPath::lineWithinBounds()
1374{
1375 const int iteration_count = 3;
1376 volatile const qreal yVal = 0.5;
1377 QPointF a(0.0, yVal);
1378 QPointF b(1000.0, yVal);
1379 QPointF c(2000.0, yVal);
1380 QPointF d(3000.0, yVal);
1381 QPainterPath path;
1382 path.moveTo(p: QPointF(0, yVal));
1383 path.cubicTo(ctrlPt1: QPointF(1000.0, yVal), ctrlPt2: QPointF(2000.0, yVal), endPt: QPointF(3000.0, yVal));
1384 for(int i=0; i<=iteration_count; i++) {
1385 qreal actual = path.pointAtPercent(t: qreal(i) / iteration_count).y();
1386 QVERIFY(actual == yVal); // don't use QCOMPARE, don't want fuzzy comparison
1387 }
1388}
1389
1390void tst_QPainterPath::intersectionEquality()
1391{
1392 // Test case from QTBUG-17027
1393 QPainterPath p1;
1394 p1.moveTo(x: 256.0000000000000000, y: 135.8384137532701743);
1395 p1.lineTo(x: 50.9999999999999715, y: 107.9999999999999857);
1396 p1.lineTo(x: 233.5425474228109123, y: 205.3560252921671462);
1397 p1.lineTo(x: 191.7771366877784373, y: 318.0257074407572304);
1398 p1.lineTo(x: -48.2616272048215151, y: 229.0459803737862216);
1399 p1.lineTo(x: 0.0000000000000000, y: 98.8515898136580801);
1400 p1.lineTo(x: 0.0000000000000000, y: 0.0000000000000000);
1401 p1.lineTo(x: 256.0000000000000000, y: 0.0000000000000000);
1402 p1.lineTo(x: 256.0000000000000000, y: 135.8384137532701743);
1403
1404 QPainterPath p2;
1405 p2.moveTo(x: 1516.2703263523442274, y: 306.9795200262722119);
1406 p2.lineTo(x: -1296.8426224886295585, y: -75.0331736542986931);
1407 p2.lineTo(x: -1678.8553161692004778, y: 2738.0797751866753060);
1408 p2.lineTo(x: 1134.2576326717733081, y: 3120.0924688672457705);
1409 p2.lineTo(x: 1516.2703263523442274, y: 306.9795200262722119);
1410
1411 QPainterPath i1 = p1.intersected(r: p2);
1412 QPainterPath i2 = p2.intersected(r: p1);
1413 QVERIFY(i1 == i2 || i1.toReversed() == i2);
1414
1415 p1 = QPainterPath();
1416 p1.moveTo(x: 256.00000000, y: 135.83841375);
1417 p1.lineTo(x: 50.99999999, y: 107.99999999);
1418 p1.lineTo(x: 233.54254742, y: 205.35602529);
1419 p1.lineTo(x: 191.77713668, y: 318.02570744);
1420 p1.lineTo(x: -48.26162720, y: 229.04598037);
1421 p1.lineTo(x: 0.00000000, y: 98.85158981);
1422 p1.lineTo(x: 0.00000000, y: 0.00000000);
1423 p1.lineTo(x: 256.00000000, y: 0.00000000);
1424 p1.lineTo(x: 256.00000000, y: 135.83841375);
1425
1426 p2 = QPainterPath();
1427 p2.moveTo(x: 1516.27032635, y: 306.97952002);
1428 p2.lineTo(x: -1296.84262248, y: -75.03317365);
1429 p2.lineTo(x: -1678.85531616, y: 2738.07977518);
1430 p2.lineTo(x: 1134.25763267, y: 3120.09246886);
1431 p2.lineTo(x: 1516.27032635, y: 306.97952002);
1432
1433 i1 = p1.intersected(r: p2);
1434 i2 = p2.intersected(r: p1);
1435 QVERIFY(i1 == i2 || i1.toReversed() == i2);
1436}
1437
1438void tst_QPainterPath::intersectionPointOnEdge()
1439{
1440 // From QTBUG-31551
1441 QPainterPath p; p.addRoundedRect(x: -10, y: 10, w: 40, h: 40, xRadius: 10, yRadius: 10);
1442 QRectF r(0, 0, 100, 100);
1443 QPainterPath rp; rp.addRect(rect: r);
1444 QVERIFY(!p.intersected(rp).isEmpty());
1445 QVERIFY(p.intersects(rp));
1446 QVERIFY(p.intersects(r));
1447}
1448
1449QTEST_APPLESS_MAIN(tst_QPainterPath)
1450
1451#include "tst_qpainterpath.moc"
1452

source code of qtbase/tests/auto/gui/painting/qpainterpath/tst_qpainterpath.cpp