1 #include <clutter/clutter.h>
2 #include <cairo.h>
3 #include <string.h>
4 #include <math.h>
5
6 #include "test-conform-common.h"
7
8 #define MAX_NODES 128
9
10 #define FLOAT_FUZZ_AMOUNT 5.0f
11
12 typedef struct _CallbackData CallbackData;
13
14 typedef gboolean (* PathTestFunc) (CallbackData *data);
15
16 static void compare_node (const ClutterPathNode *node, gpointer data_p);
17
18 struct _CallbackData
19 {
20 ClutterPath *path;
21
22 guint n_nodes;
23 ClutterPathNode nodes[MAX_NODES];
24
25 gboolean nodes_different;
26 guint nodes_found;
27 };
28
29 static const char path_desc[] =
30 "M 21 22 "
31 "L 25 26 "
32 "C 29 30 31 32 33 34 "
33 "m 23 24 "
34 "l 27 28 "
35 "c 35 36 37 38 39 40 "
36 "z";
37 static const ClutterPathNode path_nodes[] =
38 { { CLUTTER_PATH_MOVE_TO, { { 21, 22 }, { 0, 0 }, { 0, 0 } } },
39 { CLUTTER_PATH_LINE_TO, { { 25, 26 }, { 0, 0 }, { 0, 0 } } },
40 { CLUTTER_PATH_CURVE_TO, { { 29, 30 }, { 31, 32 }, { 33, 34 } } },
41 { CLUTTER_PATH_REL_MOVE_TO, { { 23, 24 }, { 0, 0 }, { 0, 0 } } },
42 { CLUTTER_PATH_REL_LINE_TO, { { 27, 28 }, { 0, 0 }, { 0, 0 } } },
43 { CLUTTER_PATH_REL_CURVE_TO, { { 35, 36 }, { 37, 38 }, { 39, 40 } } },
44 { CLUTTER_PATH_CLOSE, { { 0, 0 }, { 0, 0 }, { 0, 0 } } } };
45
46 static gboolean
path_test_add_move_to(CallbackData * data)47 path_test_add_move_to (CallbackData *data)
48 {
49 ClutterPathNode node = { 0, };
50
51 node.type = CLUTTER_PATH_MOVE_TO;
52 node.points[0].x = 1;
53 node.points[0].y = 2;
54
55 clutter_path_add_move_to (data->path, node.points[0].x, node.points[0].y);
56
57 data->nodes[data->n_nodes++] = node;
58
59 return TRUE;
60 }
61
62 static gboolean
path_test_add_line_to(CallbackData * data)63 path_test_add_line_to (CallbackData *data)
64 {
65 ClutterPathNode node = { 0, };
66
67 node.type = CLUTTER_PATH_LINE_TO;
68 node.points[0].x = 3;
69 node.points[0].y = 4;
70
71 clutter_path_add_line_to (data->path, node.points[0].x, node.points[0].y);
72
73 data->nodes[data->n_nodes++] = node;
74
75 return TRUE;
76 }
77
78 static gboolean
path_test_add_curve_to(CallbackData * data)79 path_test_add_curve_to (CallbackData *data)
80 {
81 ClutterPathNode node = { 0, };
82
83 node.type = CLUTTER_PATH_CURVE_TO;
84 node.points[0].x = 5;
85 node.points[0].y = 6;
86 node.points[1].x = 7;
87 node.points[1].y = 8;
88 node.points[2].x = 9;
89 node.points[2].y = 10;
90
91 clutter_path_add_curve_to (data->path,
92 node.points[0].x, node.points[0].y,
93 node.points[1].x, node.points[1].y,
94 node.points[2].x, node.points[2].y);
95
96 data->nodes[data->n_nodes++] = node;
97
98 return TRUE;
99 }
100
101 static gboolean
path_test_add_close(CallbackData * data)102 path_test_add_close (CallbackData *data)
103 {
104 ClutterPathNode node = { 0, };
105
106 node.type = CLUTTER_PATH_CLOSE;
107
108 clutter_path_add_close (data->path);
109
110 data->nodes[data->n_nodes++] = node;
111
112 return TRUE;
113 }
114
115 static gboolean
path_test_add_rel_move_to(CallbackData * data)116 path_test_add_rel_move_to (CallbackData *data)
117 {
118 ClutterPathNode node = { 0, };
119
120 node.type = CLUTTER_PATH_REL_MOVE_TO;
121 node.points[0].x = 11;
122 node.points[0].y = 12;
123
124 clutter_path_add_rel_move_to (data->path, node.points[0].x, node.points[0].y);
125
126 data->nodes[data->n_nodes++] = node;
127
128 return TRUE;
129 }
130
131 static gboolean
path_test_add_rel_line_to(CallbackData * data)132 path_test_add_rel_line_to (CallbackData *data)
133 {
134 ClutterPathNode node = { 0, };
135
136 node.type = CLUTTER_PATH_REL_LINE_TO;
137 node.points[0].x = 13;
138 node.points[0].y = 14;
139
140 clutter_path_add_rel_line_to (data->path, node.points[0].x, node.points[0].y);
141
142 data->nodes[data->n_nodes++] = node;
143
144 return TRUE;
145 }
146
147 static gboolean
path_test_add_rel_curve_to(CallbackData * data)148 path_test_add_rel_curve_to (CallbackData *data)
149 {
150 ClutterPathNode node = { 0, };
151
152 node.type = CLUTTER_PATH_REL_CURVE_TO;
153 node.points[0].x = 15;
154 node.points[0].y = 16;
155 node.points[1].x = 17;
156 node.points[1].y = 18;
157 node.points[2].x = 19;
158 node.points[2].y = 20;
159
160 clutter_path_add_rel_curve_to (data->path,
161 node.points[0].x, node.points[0].y,
162 node.points[1].x, node.points[1].y,
163 node.points[2].x, node.points[2].y);
164
165 data->nodes[data->n_nodes++] = node;
166
167 return TRUE;
168 }
169
170 static gboolean
path_test_add_string(CallbackData * data)171 path_test_add_string (CallbackData *data)
172 {
173 int i;
174
175 for (i = 0; i < G_N_ELEMENTS (path_nodes); i++)
176 data->nodes[data->n_nodes++] = path_nodes[i];
177
178 clutter_path_add_string (data->path, path_desc);
179
180 return TRUE;
181 }
182
183 static gboolean
path_test_add_node_by_struct(CallbackData * data)184 path_test_add_node_by_struct (CallbackData *data)
185 {
186 int i;
187
188 for (i = 0; i < G_N_ELEMENTS (path_nodes); i++)
189 {
190 data->nodes[data->n_nodes++] = path_nodes[i];
191 clutter_path_add_node (data->path, path_nodes + i);
192 }
193
194 return TRUE;
195 }
196
197 static gboolean
path_test_get_n_nodes(CallbackData * data)198 path_test_get_n_nodes (CallbackData *data)
199 {
200 return clutter_path_get_n_nodes (data->path) == data->n_nodes;
201 }
202
203 static gboolean
path_test_get_node(CallbackData * data)204 path_test_get_node (CallbackData *data)
205 {
206 int i;
207
208 data->nodes_found = 0;
209 data->nodes_different = FALSE;
210
211 for (i = 0; i < data->n_nodes; i++)
212 {
213 ClutterPathNode node;
214
215 clutter_path_get_node (data->path, i, &node);
216
217 compare_node (&node, data);
218 }
219
220 return !data->nodes_different;
221 }
222
223 static gboolean
path_test_get_nodes(CallbackData * data)224 path_test_get_nodes (CallbackData *data)
225 {
226 GSList *list, *node;
227
228 data->nodes_found = 0;
229 data->nodes_different = FALSE;
230
231 list = clutter_path_get_nodes (data->path);
232
233 for (node = list; node; node = node->next)
234 compare_node (node->data, data);
235
236 g_slist_free (list);
237
238 return !data->nodes_different && data->nodes_found == data->n_nodes;
239 }
240
241 static gboolean
path_test_insert_beginning(CallbackData * data)242 path_test_insert_beginning (CallbackData *data)
243 {
244 ClutterPathNode node;
245
246 node.type = CLUTTER_PATH_LINE_TO;
247 node.points[0].x = 41;
248 node.points[0].y = 42;
249
250 memmove (data->nodes + 1, data->nodes,
251 data->n_nodes++ * sizeof (ClutterPathNode));
252 data->nodes[0] = node;
253
254 clutter_path_insert_node (data->path, 0, &node);
255
256 return TRUE;
257 }
258
259 static gboolean
path_test_insert_end(CallbackData * data)260 path_test_insert_end (CallbackData *data)
261 {
262 ClutterPathNode node;
263
264 node.type = CLUTTER_PATH_LINE_TO;
265 node.points[0].x = 43;
266 node.points[0].y = 44;
267
268 data->nodes[data->n_nodes++] = node;
269
270 clutter_path_insert_node (data->path, -1, &node);
271
272 return TRUE;
273 }
274
275 static gboolean
path_test_insert_middle(CallbackData * data)276 path_test_insert_middle (CallbackData *data)
277 {
278 ClutterPathNode node;
279 int pos = data->n_nodes / 2;
280
281 node.type = CLUTTER_PATH_LINE_TO;
282 node.points[0].x = 45;
283 node.points[0].y = 46;
284
285 memmove (data->nodes + pos + 1, data->nodes + pos,
286 (data->n_nodes - pos) * sizeof (ClutterPathNode));
287 data->nodes[pos] = node;
288 data->n_nodes++;
289
290 clutter_path_insert_node (data->path, pos, &node);
291
292 return TRUE;
293 }
294
295 static gboolean
path_test_clear(CallbackData * data)296 path_test_clear (CallbackData *data)
297 {
298 clutter_path_clear (data->path);
299
300 data->n_nodes = 0;
301
302 return TRUE;
303 }
304
305 static gboolean
path_test_clear_insert(CallbackData * data)306 path_test_clear_insert (CallbackData *data)
307 {
308 return path_test_clear (data) && path_test_insert_middle (data);
309 }
310
311 static gboolean
path_test_remove_beginning(CallbackData * data)312 path_test_remove_beginning (CallbackData *data)
313 {
314 memmove (data->nodes, data->nodes + 1,
315 --data->n_nodes * sizeof (ClutterPathNode));
316
317 clutter_path_remove_node (data->path, 0);
318
319 return TRUE;
320 }
321
322 static gboolean
path_test_remove_end(CallbackData * data)323 path_test_remove_end (CallbackData *data)
324 {
325 clutter_path_remove_node (data->path, --data->n_nodes);
326
327 return TRUE;
328 }
329
330 static gboolean
path_test_remove_middle(CallbackData * data)331 path_test_remove_middle (CallbackData *data)
332 {
333 int pos = data->n_nodes / 2;
334
335 memmove (data->nodes + pos, data->nodes + pos + 1,
336 (--data->n_nodes - pos) * sizeof (ClutterPathNode));
337
338 clutter_path_remove_node (data->path, pos);
339
340 return TRUE;
341 }
342
343 static gboolean
path_test_remove_only(CallbackData * data)344 path_test_remove_only (CallbackData *data)
345 {
346 return path_test_clear (data)
347 && path_test_add_line_to (data)
348 && path_test_remove_beginning (data);
349 }
350
351 static gboolean
path_test_replace(CallbackData * data)352 path_test_replace (CallbackData *data)
353 {
354 ClutterPathNode node;
355 int pos = data->n_nodes / 2;
356
357 node.type = CLUTTER_PATH_LINE_TO;
358 node.points[0].x = 47;
359 node.points[0].y = 48;
360
361 data->nodes[pos] = node;
362
363 clutter_path_replace_node (data->path, pos, &node);
364
365 return TRUE;
366 }
367
368 static gboolean
path_test_set_description(CallbackData * data)369 path_test_set_description (CallbackData *data)
370 {
371 data->n_nodes = G_N_ELEMENTS (path_nodes);
372 memcpy (data->nodes, path_nodes, sizeof (path_nodes));
373
374 return clutter_path_set_description (data->path, path_desc);
375 }
376
377 static gboolean
path_test_get_description(CallbackData * data)378 path_test_get_description (CallbackData *data)
379 {
380 char *desc1, *desc2;
381 gboolean ret = TRUE;
382
383 desc1 = clutter_path_get_description (data->path);
384 clutter_path_clear (data->path);
385 if (!clutter_path_set_description (data->path, desc1))
386 ret = FALSE;
387 desc2 = clutter_path_get_description (data->path);
388
389 if (strcmp (desc1, desc2))
390 ret = FALSE;
391
392 g_free (desc1);
393 g_free (desc2);
394
395 return ret;
396 }
397
398 static gboolean
path_test_convert_to_cairo_path(CallbackData * data)399 path_test_convert_to_cairo_path (CallbackData *data)
400 {
401 cairo_surface_t *surface;
402 cairo_t *cr;
403 cairo_path_t *cpath;
404 guint i, j;
405 ClutterKnot path_start = { 0, 0 }, last_point = { 0, 0 };
406
407 /* Create a temporary image surface and context to hold the cairo
408 path */
409 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10);
410 cr = cairo_create (surface);
411
412 /* Convert to a cairo path */
413 clutter_path_to_cairo_path (data->path, cr);
414
415 /* Get a copy of the cairo path data */
416 cpath = cairo_copy_path (cr);
417
418 /* Convert back to a clutter path */
419 clutter_path_clear (data->path);
420 clutter_path_add_cairo_path (data->path, cpath);
421
422 /* The relative nodes will have been converted to absolute so we
423 need to reflect this in the node array for comparison */
424 for (i = 0; i < data->n_nodes; i++)
425 {
426 switch (data->nodes[i].type)
427 {
428 case CLUTTER_PATH_MOVE_TO:
429 path_start = last_point = data->nodes[i].points[0];
430 break;
431
432 case CLUTTER_PATH_LINE_TO:
433 last_point = data->nodes[i].points[0];
434 break;
435
436 case CLUTTER_PATH_CURVE_TO:
437 last_point = data->nodes[i].points[2];
438 break;
439
440 case CLUTTER_PATH_REL_MOVE_TO:
441 last_point.x += data->nodes[i].points[0].x;
442 last_point.y += data->nodes[i].points[0].y;
443 data->nodes[i].points[0] = last_point;
444 data->nodes[i].type = CLUTTER_PATH_MOVE_TO;
445 path_start = last_point;
446 break;
447
448 case CLUTTER_PATH_REL_LINE_TO:
449 last_point.x += data->nodes[i].points[0].x;
450 last_point.y += data->nodes[i].points[0].y;
451 data->nodes[i].points[0] = last_point;
452 data->nodes[i].type = CLUTTER_PATH_LINE_TO;
453 break;
454
455 case CLUTTER_PATH_REL_CURVE_TO:
456 for (j = 0; j < 3; j++)
457 {
458 data->nodes[i].points[j].x += last_point.x;
459 data->nodes[i].points[j].y += last_point.y;
460 }
461 last_point = data->nodes[i].points[2];
462 data->nodes[i].type = CLUTTER_PATH_CURVE_TO;
463 break;
464
465 case CLUTTER_PATH_CLOSE:
466 last_point = path_start;
467
468 /* Cairo always adds a move to after every close so we need
469 to insert one here. Since Cairo commit 166453c1abf2 it
470 doesn't seem to do this anymore so will assume that if
471 Cairo's minor version is >= 11 then it includes that
472 commit */
473 if (cairo_version () < CAIRO_VERSION_ENCODE (1, 11, 0))
474 {
475 memmove (data->nodes + i + 2, data->nodes + i + 1,
476 (data->n_nodes - i - 1) * sizeof (ClutterPathNode));
477 data->nodes[i + 1].type = CLUTTER_PATH_MOVE_TO;
478 data->nodes[i + 1].points[0] = last_point;
479 data->n_nodes++;
480 }
481 break;
482 }
483 }
484
485 /* Free the cairo resources */
486 cairo_path_destroy (cpath);
487 cairo_destroy (cr);
488 cairo_surface_destroy (surface);
489
490 return TRUE;
491 }
492
493 static gboolean
float_fuzzy_equals(float fa,float fb)494 float_fuzzy_equals (float fa, float fb)
495 {
496 return fabs (fa - fb) <= FLOAT_FUZZ_AMOUNT;
497 }
498
499 static void
set_triangle_path(CallbackData * data)500 set_triangle_path (CallbackData *data)
501 {
502 /* Triangular shaped path hitting (0,0), (64,64) and (128,0) in four
503 parts. The two curves are actually straight lines */
504 static const ClutterPathNode nodes[] =
505 { { CLUTTER_PATH_MOVE_TO, { { 0, 0 } } },
506 { CLUTTER_PATH_LINE_TO, { { 32, 32 } } },
507 { CLUTTER_PATH_CURVE_TO, { { 40, 40 }, { 56, 56 }, { 64, 64 } } },
508 { CLUTTER_PATH_REL_CURVE_TO, { { 8, -8 }, { 24, -24 }, { 32, -32 } } },
509 { CLUTTER_PATH_REL_LINE_TO, { { 32, -32 } } } };
510 gint i;
511
512 clutter_path_clear (data->path);
513
514 for (i = 0; i < G_N_ELEMENTS (nodes); i++)
515 clutter_path_add_node (data->path, nodes + i);
516
517 memcpy (data->nodes, nodes, sizeof (nodes));
518 data->n_nodes = G_N_ELEMENTS (nodes);
519 }
520
521 static gboolean
path_test_get_position(CallbackData * data)522 path_test_get_position (CallbackData *data)
523 {
524 static const float values[] = { 0.125f, 16.0f, 16.0f,
525 0.375f, 48.0f, 48.0f,
526 0.625f, 80.0f, 48.0f,
527 0.875f, 112.0f, 16.0f };
528 gint i;
529
530 set_triangle_path (data);
531
532 for (i = 0; i < G_N_ELEMENTS (values); i += 3)
533 {
534 ClutterKnot pos;
535
536 clutter_path_get_position (data->path,
537 values[i],
538 &pos);
539
540 if (!float_fuzzy_equals (values[i + 1], pos.x)
541 || !float_fuzzy_equals (values[i + 2], pos.y))
542 return FALSE;
543 }
544
545 return TRUE;
546 }
547
548 static gboolean
path_test_get_length(CallbackData * data)549 path_test_get_length (CallbackData *data)
550 {
551 const float actual_length /* sqrt(64**2 + 64**2) * 2 */ = 181.019336f;
552 guint approx_length;
553
554 clutter_path_set_description (data->path, "M 0 0 L 46340 0");
555 g_object_get (data->path, "length", &approx_length, NULL);
556
557 if (!(fabs (approx_length - 46340.f) / 46340.f <= 0.15f))
558 {
559 if (!g_test_quiet ())
560 g_print ("M 0 0 L 46340 0 - Expected 46340, got %d instead.", approx_length);
561
562 return FALSE;
563 }
564
565 clutter_path_set_description (data->path, "M 0 0 L 46341 0");
566 g_object_get (data->path, "length", &approx_length, NULL);
567
568 if (!(fabs (approx_length - 46341.f) / 46341.f <= 0.15f))
569 {
570 if (!g_test_quiet ())
571 g_print ("M 0 0 L 46341 0 - Expected 46341, got %d instead.", approx_length);
572
573 return FALSE;
574 }
575
576 set_triangle_path (data);
577
578 g_object_get (data->path, "length", &approx_length, NULL);
579
580 /* Allow 15% margin of error */
581 if (!(fabs (approx_length - actual_length) / (float) actual_length <= 0.15f))
582 {
583 if (!g_test_quiet ())
584 g_print ("Expected %g, got %d instead.\n", actual_length, approx_length);
585
586 return FALSE;
587 }
588
589 return TRUE;
590 }
591
592 static gboolean
path_test_boxed_type(CallbackData * data)593 path_test_boxed_type (CallbackData *data)
594 {
595 gboolean ret = TRUE;
596 GSList *nodes, *l;
597 GValue value;
598
599 nodes = clutter_path_get_nodes (data->path);
600
601 memset (&value, 0, sizeof (value));
602
603 for (l = nodes; l; l = l->next)
604 {
605 g_value_init (&value, CLUTTER_TYPE_PATH_NODE);
606
607 g_value_set_boxed (&value, l->data);
608
609 if (!clutter_path_node_equal (l->data,
610 g_value_get_boxed (&value)))
611 ret = FALSE;
612
613 g_value_unset (&value);
614 }
615
616 g_slist_free (nodes);
617
618 return ret;
619 }
620
621 static const struct
622 {
623 const char *desc;
624 PathTestFunc func;
625 }
626 path_tests[] =
627 {
628 { "Add line to", path_test_add_line_to },
629 { "Add move to", path_test_add_move_to },
630 { "Add curve to", path_test_add_curve_to },
631 { "Add close", path_test_add_close },
632 { "Add relative line to", path_test_add_rel_line_to },
633 { "Add relative move to", path_test_add_rel_move_to },
634 { "Add relative curve to", path_test_add_rel_curve_to },
635 { "Add string", path_test_add_string },
636 { "Add node by struct", path_test_add_node_by_struct },
637 { "Get number of nodes", path_test_get_n_nodes },
638 { "Get a node", path_test_get_node },
639 { "Get all nodes", path_test_get_nodes },
640 { "Insert at beginning", path_test_insert_beginning },
641 { "Insert at end", path_test_insert_end },
642 { "Insert at middle", path_test_insert_middle },
643 { "Add after insert", path_test_add_line_to },
644 { "Clear then insert", path_test_clear_insert },
645 { "Add string again", path_test_add_string },
646 { "Remove from beginning", path_test_remove_beginning },
647 { "Remove from end", path_test_remove_end },
648 { "Remove from middle", path_test_remove_middle },
649 { "Add after remove", path_test_add_line_to },
650 { "Remove only node", path_test_remove_only },
651 { "Add after remove again", path_test_add_line_to },
652 { "Replace a node", path_test_replace },
653 { "Set description", path_test_set_description },
654 { "Get description", path_test_get_description },
655 { "Convert to cairo path and back", path_test_convert_to_cairo_path },
656 { "Clear", path_test_clear },
657 { "Get position", path_test_get_position },
658 { "Check node boxed type", path_test_boxed_type },
659 { "Get length", path_test_get_length }
660 };
661
662 static void
compare_node(const ClutterPathNode * node,gpointer data_p)663 compare_node (const ClutterPathNode *node, gpointer data_p)
664 {
665 CallbackData *data = data_p;
666
667 if (data->nodes_found >= data->n_nodes)
668 data->nodes_different = TRUE;
669 else
670 {
671 guint n_points = 0, i;
672 const ClutterPathNode *onode = data->nodes + data->nodes_found;
673
674 if (node->type != onode->type)
675 data->nodes_different = TRUE;
676
677 switch (node->type & ~CLUTTER_PATH_RELATIVE)
678 {
679 case CLUTTER_PATH_MOVE_TO: n_points = 1; break;
680 case CLUTTER_PATH_LINE_TO: n_points = 1; break;
681 case CLUTTER_PATH_CURVE_TO: n_points = 3; break;
682 case CLUTTER_PATH_CLOSE: n_points = 0; break;
683
684 default:
685 data->nodes_different = TRUE;
686 break;
687 }
688
689 for (i = 0; i < n_points; i++)
690 if (node->points[i].x != onode->points[i].x
691 || node->points[i].y != onode->points[i].y)
692 {
693 data->nodes_different = TRUE;
694 break;
695 }
696 }
697
698 data->nodes_found++;
699 }
700
701 static gboolean
compare_nodes(CallbackData * data)702 compare_nodes (CallbackData *data)
703 {
704 data->nodes_different = FALSE;
705 data->nodes_found = 0;
706
707 clutter_path_foreach (data->path, compare_node, data);
708
709 return !data->nodes_different && data->nodes_found == data->n_nodes;
710 }
711
712 void
path_base(TestConformSimpleFixture * fixture,gconstpointer _data)713 path_base (TestConformSimpleFixture *fixture,
714 gconstpointer _data)
715 {
716 CallbackData data;
717 gint i;
718
719 memset (&data, 0, sizeof (data));
720
721 data.path = clutter_path_new ();
722
723 for (i = 0; i < G_N_ELEMENTS (path_tests); i++)
724 {
725 gboolean succeeded;
726
727 if (!g_test_quiet ())
728 g_print ("%s... ", path_tests[i].desc);
729
730 succeeded = path_tests[i].func (&data) && compare_nodes (&data);
731
732 if (!g_test_quiet ())
733 g_print ("%s\n", succeeded ? "ok" : "FAIL");
734
735 g_assert (succeeded);
736 }
737
738 g_object_unref (data.path);
739 }
740
741