update to 1.10.4
[profile/ivi/clutter.git] / tests / conform / path.c
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
306 path_test_clear_insert (CallbackData *data)
307 {
308   return path_test_clear (data) && path_test_insert_middle (data);
309 }
310
311 static gboolean
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
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
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
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
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
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
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
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
494 float_fuzzy_equals (float fa, float fb)
495 {
496   return fabs (fa - fb) <= FLOAT_FUZZ_AMOUNT;
497 }
498
499 static void
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
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
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_verbose ())
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_verbose ())
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_verbose ())
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
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
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
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
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_verbose ())
728         g_print ("%s... ", path_tests[i].desc);
729
730       succeeded = path_tests[i].func (&data) && compare_nodes (&data);
731
732       if (g_test_verbose ())
733         g_print ("%s\n", succeeded ? "ok" : "FAIL");
734
735       g_assert (succeeded);
736     }
737
738   g_object_unref (data.path);
739 }
740