mutter/tests/conform/test-path.c

705 lines
17 KiB
C
Raw Normal View History

#include <clutter/clutter.h>
#include <cairo/cairo.h>
#include <string.h>
#include <math.h>
#include "test-conform-common.h"
#define MAX_NODES 128
#define FLOAT_FUZZ_AMOUNT 5.0f
typedef struct _CallbackData CallbackData;
typedef gboolean (* PathTestFunc) (CallbackData *data);
static void compare_node (const ClutterPathNode *node, gpointer data_p);
struct _CallbackData
{
ClutterPath *path;
guint n_nodes;
ClutterPathNode nodes[MAX_NODES];
gboolean nodes_different;
guint nodes_found;
};
static const char path_desc[] =
"M 21 22 "
"L 25 26 "
"C 29 30 31 32 33 34 "
"m 23 24 "
"l 27 28 "
"c 35 36 37 38 39 40 "
"z";
static const ClutterPathNode path_nodes[] =
{ { CLUTTER_PATH_MOVE_TO, { { 21, 22 }, { 0, 0 }, { 0, 0 } } },
{ CLUTTER_PATH_LINE_TO, { { 25, 26 }, { 0, 0 }, { 0, 0 } } },
{ CLUTTER_PATH_CURVE_TO, { { 29, 30 }, { 31, 32 }, { 33, 34 } } },
{ CLUTTER_PATH_REL_MOVE_TO, { { 23, 24 }, { 0, 0 }, { 0, 0 } } },
{ CLUTTER_PATH_REL_LINE_TO, { { 27, 28 }, { 0, 0 }, { 0, 0 } } },
{ CLUTTER_PATH_REL_CURVE_TO, { { 35, 36 }, { 37, 38 }, { 39, 40 } } },
{ CLUTTER_PATH_CLOSE, { { 0, 0 }, { 0, 0 }, { 0, 0 } } } };
static gboolean
path_test_add_move_to (CallbackData *data)
{
ClutterPathNode node;
node.type = CLUTTER_PATH_MOVE_TO;
node.points[0].x = 1;
node.points[0].y = 2;
clutter_path_add_move_to (data->path, node.points[0].x, node.points[0].y);
data->nodes[data->n_nodes++] = node;
return TRUE;
}
static gboolean
path_test_add_line_to (CallbackData *data)
{
ClutterPathNode node;
node.type = CLUTTER_PATH_LINE_TO;
node.points[0].x = 3;
node.points[0].y = 4;
clutter_path_add_line_to (data->path, node.points[0].x, node.points[0].y);
data->nodes[data->n_nodes++] = node;
return TRUE;
}
static gboolean
path_test_add_curve_to (CallbackData *data)
{
ClutterPathNode node;
node.type = CLUTTER_PATH_CURVE_TO;
node.points[0].x = 5;
node.points[0].y = 6;
node.points[1].x = 7;
node.points[1].y = 8;
node.points[2].x = 9;
node.points[2].y = 10;
clutter_path_add_curve_to (data->path,
node.points[0].x, node.points[0].y,
node.points[1].x, node.points[1].y,
node.points[2].x, node.points[2].y);
data->nodes[data->n_nodes++] = node;
return TRUE;
}
static gboolean
path_test_add_close (CallbackData *data)
{
ClutterPathNode node;
node.type = CLUTTER_PATH_CLOSE;
clutter_path_add_close (data->path);
data->nodes[data->n_nodes++] = node;
return TRUE;
}
static gboolean
path_test_add_rel_move_to (CallbackData *data)
{
ClutterPathNode node;
node.type = CLUTTER_PATH_REL_MOVE_TO;
node.points[0].x = 11;
node.points[0].y = 12;
clutter_path_add_rel_move_to (data->path, node.points[0].x, node.points[0].y);
data->nodes[data->n_nodes++] = node;
return TRUE;
}
static gboolean
path_test_add_rel_line_to (CallbackData *data)
{
ClutterPathNode node;
node.type = CLUTTER_PATH_REL_LINE_TO;
node.points[0].x = 13;
node.points[0].y = 14;
clutter_path_add_rel_line_to (data->path, node.points[0].x, node.points[0].y);
data->nodes[data->n_nodes++] = node;
return TRUE;
}
static gboolean
path_test_add_rel_curve_to (CallbackData *data)
{
ClutterPathNode node;
node.type = CLUTTER_PATH_REL_CURVE_TO;
node.points[0].x = 15;
node.points[0].y = 16;
node.points[1].x = 17;
node.points[1].y = 18;
node.points[2].x = 19;
node.points[2].y = 20;
clutter_path_add_rel_curve_to (data->path,
node.points[0].x, node.points[0].y,
node.points[1].x, node.points[1].y,
node.points[2].x, node.points[2].y);
data->nodes[data->n_nodes++] = node;
return TRUE;
}
static gboolean
path_test_add_string (CallbackData *data)
{
int i;
for (i = 0; i < G_N_ELEMENTS (path_nodes); i++)
data->nodes[data->n_nodes++] = path_nodes[i];
clutter_path_add_string (data->path, path_desc);
return TRUE;
}
static gboolean
path_test_add_node_by_struct (CallbackData *data)
{
int i;
for (i = 0; i < G_N_ELEMENTS (path_nodes); i++)
{
data->nodes[data->n_nodes++] = path_nodes[i];
clutter_path_add_node (data->path, path_nodes + i);
}
return TRUE;
}
static gboolean
path_test_get_n_nodes (CallbackData *data)
{
return clutter_path_get_n_nodes (data->path) == data->n_nodes;
}
static gboolean
path_test_get_node (CallbackData *data)
{
int i;
data->nodes_found = 0;
data->nodes_different = FALSE;
for (i = 0; i < data->n_nodes; i++)
{
ClutterPathNode node;
clutter_path_get_node (data->path, i, &node);
compare_node (&node, data);
}
return !data->nodes_different;
}
static gboolean
path_test_get_nodes (CallbackData *data)
{
GSList *list, *node;
data->nodes_found = 0;
data->nodes_different = FALSE;
list = clutter_path_get_nodes (data->path);
for (node = list; node; node = node->next)
compare_node (node->data, data);
g_slist_free (list);
return !data->nodes_different && data->nodes_found == data->n_nodes;
}
static gboolean
path_test_insert_beginning (CallbackData *data)
{
ClutterPathNode node;
node.type = CLUTTER_PATH_LINE_TO;
node.points[0].x = 41;
node.points[0].y = 42;
memmove (data->nodes + 1, data->nodes,
data->n_nodes++ * sizeof (ClutterPathNode));
data->nodes[0] = node;
clutter_path_insert_node (data->path, 0, &node);
return TRUE;
}
static gboolean
path_test_insert_end (CallbackData *data)
{
ClutterPathNode node;
node.type = CLUTTER_PATH_LINE_TO;
node.points[0].x = 43;
node.points[0].y = 44;
data->nodes[data->n_nodes++] = node;
clutter_path_insert_node (data->path, -1, &node);
return TRUE;
}
static gboolean
path_test_insert_middle (CallbackData *data)
{
ClutterPathNode node;
int pos = data->n_nodes / 2;
node.type = CLUTTER_PATH_LINE_TO;
node.points[0].x = 45;
node.points[0].y = 46;
memmove (data->nodes + pos + 1, data->nodes + pos,
(data->n_nodes - pos) * sizeof (ClutterPathNode));
data->nodes[pos] = node;
data->n_nodes++;
clutter_path_insert_node (data->path, pos, &node);
return TRUE;
}
static gboolean
path_test_clear (CallbackData *data)
{
clutter_path_clear (data->path);
data->n_nodes = 0;
return TRUE;
}
static gboolean
path_test_clear_insert (CallbackData *data)
{
return path_test_clear (data) && path_test_insert_middle (data);
}
static gboolean
path_test_remove_beginning (CallbackData *data)
{
memmove (data->nodes, data->nodes + 1,
--data->n_nodes * sizeof (ClutterPathNode));
clutter_path_remove_node (data->path, 0);
return TRUE;
}
static gboolean
path_test_remove_end (CallbackData *data)
{
clutter_path_remove_node (data->path, --data->n_nodes);
return TRUE;
}
static gboolean
path_test_remove_middle (CallbackData *data)
{
int pos = data->n_nodes / 2;
memmove (data->nodes + pos, data->nodes + pos + 1,
(--data->n_nodes - pos) * sizeof (ClutterPathNode));
clutter_path_remove_node (data->path, pos);
return TRUE;
}
static gboolean
path_test_remove_only (CallbackData *data)
{
return path_test_clear (data)
&& path_test_add_line_to (data)
&& path_test_remove_beginning (data);
}
static gboolean
path_test_replace (CallbackData *data)
{
ClutterPathNode node;
int pos = data->n_nodes / 2;
node.type = CLUTTER_PATH_LINE_TO;
node.points[0].x = 47;
node.points[0].y = 48;
data->nodes[pos] = node;
clutter_path_replace_node (data->path, pos, &node);
return TRUE;
}
static gboolean
path_test_set_description (CallbackData *data)
{
data->n_nodes = G_N_ELEMENTS (path_nodes);
memcpy (data->nodes, path_nodes, sizeof (path_nodes));
return clutter_path_set_description (data->path, path_desc);
}
static gboolean
path_test_get_description (CallbackData *data)
{
char *desc1, *desc2;
gboolean ret = TRUE;
desc1 = clutter_path_get_description (data->path);
clutter_path_clear (data->path);
if (!clutter_path_set_description (data->path, desc1))
ret = FALSE;
desc2 = clutter_path_get_description (data->path);
if (strcmp (desc1, desc2))
ret = FALSE;
g_free (desc1);
g_free (desc2);
return ret;
}
static gboolean
path_test_convert_to_cairo_path (CallbackData *data)
{
cairo_surface_t *surface;
cairo_t *cr;
cairo_path_t *cpath;
guint i, j;
ClutterKnot path_start = { 0, 0 }, last_point = { 0, 0 };
/* Create a temporary image surface and context to hold the cairo
path */
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10);
cr = cairo_create (surface);
/* Convert to a cairo path */
clutter_path_to_cairo_path (data->path, cr);
/* Get a copy of the cairo path data */
cpath = cairo_copy_path (cr);
/* Convert back to a clutter path */
clutter_path_clear (data->path);
clutter_path_add_cairo_path (data->path, cpath);
/* The relative nodes will have been converted to absolute so we
need to reflect this in the node array for comparison */
for (i = 0; i < data->n_nodes; i++)
{
switch (data->nodes[i].type)
{
case CLUTTER_PATH_MOVE_TO:
path_start = last_point = data->nodes[i].points[0];
break;
case CLUTTER_PATH_LINE_TO:
last_point = data->nodes[i].points[0];
break;
case CLUTTER_PATH_CURVE_TO:
last_point = data->nodes[i].points[2];
break;
case CLUTTER_PATH_REL_MOVE_TO:
last_point.x += data->nodes[i].points[0].x;
last_point.y += data->nodes[i].points[0].y;
data->nodes[i].points[0] = last_point;
data->nodes[i].type = CLUTTER_PATH_MOVE_TO;
path_start = last_point;
break;
case CLUTTER_PATH_REL_LINE_TO:
last_point.x += data->nodes[i].points[0].x;
last_point.y += data->nodes[i].points[0].y;
data->nodes[i].points[0] = last_point;
data->nodes[i].type = CLUTTER_PATH_LINE_TO;
break;
case CLUTTER_PATH_REL_CURVE_TO:
for (j = 0; j < 3; j++)
{
data->nodes[i].points[j].x += last_point.x;
data->nodes[i].points[j].y += last_point.y;
}
last_point = data->nodes[i].points[2];
data->nodes[i].type = CLUTTER_PATH_CURVE_TO;
break;
case CLUTTER_PATH_CLOSE:
last_point = path_start;
/* Cairo always adds a move to after every close so we need
to insert one here */
memmove (data->nodes + i + 2, data->nodes + i + 1,
(data->n_nodes - i - 1) * sizeof (ClutterPathNode));
data->nodes[i + 1].type = CLUTTER_PATH_MOVE_TO;
data->nodes[i + 1].points[0] = last_point;
data->n_nodes++;
break;
}
}
/* Free the cairo resources */
cairo_path_destroy (cpath);
cairo_destroy (cr);
cairo_surface_destroy (surface);
return TRUE;
}
static gboolean
float_fuzzy_equals (float fa, float fb)
{
return fabs (fa - fb) <= FLOAT_FUZZ_AMOUNT;
}
static void
set_triangle_path (CallbackData *data)
{
/* Triangular shaped path hitting (0,0), (64,64) and (128,0) in four
parts. The two curves are actually straight lines */
static const ClutterPathNode nodes[] =
{ { CLUTTER_PATH_MOVE_TO, { { 0, 0 } } },
{ CLUTTER_PATH_LINE_TO, { { 32, 32 } } },
{ CLUTTER_PATH_CURVE_TO, { { 40, 40 }, { 56, 56 }, { 64, 64 } } },
{ CLUTTER_PATH_REL_CURVE_TO, { { 8, -8 }, { 24, -24 }, { 32, -32 } } },
{ CLUTTER_PATH_REL_LINE_TO, { { 32, -32 } } } };
gint i;
clutter_path_clear (data->path);
for (i = 0; i < G_N_ELEMENTS (nodes); i++)
clutter_path_add_node (data->path, nodes + i);
memcpy (data->nodes, nodes, sizeof (nodes));
data->n_nodes = G_N_ELEMENTS (nodes);
}
static gboolean
path_test_get_position (CallbackData *data)
{
static const float values[] = { 0.125f, 16.0f, 16.0f,
0.375f, 48.0f, 48.0f,
0.625f, 80.0f, 48.0f,
0.875f, 112.0f, 16.0f };
gint i;
set_triangle_path (data);
for (i = 0; i < G_N_ELEMENTS (values); i += 3)
{
ClutterKnot pos;
clutter_path_get_position (data->path,
values[i],
&pos);
if (!float_fuzzy_equals (values[i + 1], pos.x)
|| !float_fuzzy_equals (values[i + 2], pos.y))
return FALSE;
}
return TRUE;
}
static gboolean
path_test_get_length (CallbackData *data)
{
const float actual_length /* sqrt(64**2 + 64**2) * 2 */ = 181.019336f;
guint approx_length;
set_triangle_path (data);
g_object_get (data->path, "length", &approx_length, NULL);
/* Allow 15% margin of error */
return fabs (approx_length - actual_length) / actual_length <= 0.15f;
}
static gboolean
path_test_boxed_type (CallbackData *data)
{
gboolean ret = TRUE;
GSList *nodes, *l;
GValue value;
nodes = clutter_path_get_nodes (data->path);
memset (&value, 0, sizeof (value));
for (l = nodes; l; l = l->next)
{
g_value_init (&value, CLUTTER_TYPE_PATH_NODE);
g_value_set_boxed (&value, l->data);
if (!clutter_path_node_equal (l->data,
g_value_get_boxed (&value)))
ret = FALSE;
g_value_unset (&value);
}
g_slist_free (nodes);
return ret;
}
static const struct
{
const char *desc;
PathTestFunc func;
}
path_tests[] =
{
{ "Add line to", path_test_add_line_to },
{ "Add move to", path_test_add_move_to },
{ "Add curve to", path_test_add_curve_to },
{ "Add close", path_test_add_close },
{ "Add relative line to", path_test_add_rel_line_to },
{ "Add relative move to", path_test_add_rel_move_to },
{ "Add relative curve to", path_test_add_rel_curve_to },
{ "Add string", path_test_add_string },
{ "Add node by struct", path_test_add_node_by_struct },
{ "Get number of nodes", path_test_get_n_nodes },
{ "Get a node", path_test_get_node },
{ "Get all nodes", path_test_get_nodes },
{ "Insert at beginning", path_test_insert_beginning },
{ "Insert at end", path_test_insert_end },
{ "Insert at middle", path_test_insert_middle },
{ "Add after insert", path_test_add_line_to },
{ "Clear then insert", path_test_clear_insert },
{ "Add string again", path_test_add_string },
{ "Remove from beginning", path_test_remove_beginning },
{ "Remove from end", path_test_remove_end },
{ "Remove from middle", path_test_remove_middle },
{ "Add after remove", path_test_add_line_to },
{ "Remove only node", path_test_remove_only },
{ "Add after remove again", path_test_add_line_to },
{ "Replace a node", path_test_replace },
{ "Set description", path_test_set_description },
{ "Get description", path_test_get_description },
{ "Convert to cairo path and back", path_test_convert_to_cairo_path },
{ "Clear", path_test_clear },
{ "Get position", path_test_get_position },
{ "Check node boxed type", path_test_boxed_type },
{ "Get length", path_test_get_length }
};
static void
compare_node (const ClutterPathNode *node, gpointer data_p)
{
CallbackData *data = data_p;
if (data->nodes_found >= data->n_nodes)
data->nodes_different = TRUE;
else
{
guint n_points = 0, i;
const ClutterPathNode *onode = data->nodes + data->nodes_found;
if (node->type != onode->type)
data->nodes_different = TRUE;
switch (node->type & ~CLUTTER_PATH_RELATIVE)
{
case CLUTTER_PATH_MOVE_TO: n_points = 1; break;
case CLUTTER_PATH_LINE_TO: n_points = 1; break;
case CLUTTER_PATH_CURVE_TO: n_points = 3; break;
case CLUTTER_PATH_CLOSE: n_points = 0; break;
default:
data->nodes_different = TRUE;
break;
}
for (i = 0; i < n_points; i++)
if (node->points[i].x != onode->points[i].x
|| node->points[i].y != onode->points[i].y)
{
data->nodes_different = TRUE;
break;
}
}
data->nodes_found++;
}
static gboolean
compare_nodes (CallbackData *data)
{
data->nodes_different = FALSE;
data->nodes_found = 0;
clutter_path_foreach (data->path, compare_node, data);
return !data->nodes_different && data->nodes_found == data->n_nodes;
}
void
test_path (TestConformSimpleFixture *fixture,
gconstpointer _data)
{
CallbackData data;
gint i;
memset (&data, 0, sizeof (data));
data.path = clutter_path_new ();
for (i = 0; i < G_N_ELEMENTS (path_tests); i++)
{
gboolean succeeded;
if (g_test_verbose ())
g_print ("%s... ", path_tests[i].desc);
succeeded = path_tests[i].func (&data) && compare_nodes (&data);
if (g_test_verbose ())
g_print ("%s\n", succeeded ? "ok" : "FAIL");
g_assert (succeeded);
}
g_object_unref (data.path);
}