diff --git a/testsuite/gsk/measure-special-cases.c b/testsuite/gsk/measure-special-cases.c
new file mode 100644
index 0000000000..f25124492b
--- /dev/null
+++ b/testsuite/gsk/measure-special-cases.c
@@ -0,0 +1,262 @@
+/*
+ * Copyright © 2020 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#include
+
+
+static void
+test_bad_split (void)
+{
+ GskPath *path, *path1;
+ GskPathMeasure *measure, *measure1;
+ GskPathBuilder *builder;
+ float split, length, epsilon;
+
+ /* An example that was isolated from the /path/segment test path.c
+ * It shows how uneven parametrization of cubics can lead to bad
+ * lengths reported by the measure.
+ */
+
+ path = gsk_path_parse ("M 0 0 C 2 0 4 0 4 0");
+
+ measure = gsk_path_measure_new (path);
+ split = 2.962588;
+ length = gsk_path_measure_get_length (measure);
+ epsilon = MAX (length / 256, 1.f / 1024);
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_segment (builder, measure, 0, split);
+ path1 = gsk_path_builder_free_to_path (builder);
+ measure1 = gsk_path_measure_new (path1);
+
+ g_assert_cmpfloat_with_epsilon (split, gsk_path_measure_get_length (measure1), epsilon);
+
+ gsk_path_measure_unref (measure1);
+ gsk_path_unref (path1);
+ gsk_path_measure_unref (measure);
+ gsk_path_unref (path);
+}
+
+static void
+test_bad_in_fill (void)
+{
+ GskPath *path;
+ gboolean inside;
+
+ /* A fat Cantarell W */
+ path = gsk_path_parse ("M -2 694 M 206.1748046875 704 L 390.9371337890625 704 L 551.1888427734375 99.5035400390625 L 473.0489501953125 99.5035400390625 L 649.1048583984375 704 L 828.965087890625 704 L 1028.3077392578125 10 L 857.8111572265625 10 L 710.0489501953125 621.251708984375 L 775.9720458984375 598.426513671875 L 614.5245361328125 14.0489501953125 L 430.2237548828125 14.0489501953125 L 278.6783447265625 602.230712890625 L 330.0909423828125 602.230712890625 L 195.88818359375 10 L 5.7342529296875 10 L 206.1748046875 704 Z");
+
+ /* The midpoint of the right foot of a fat Cantarell X */
+ inside = gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (552.360107, 704.000000), GSK_FILL_RULE_WINDING);
+
+ g_assert_false (inside);
+
+ gsk_path_unref (path);
+}
+
+/* Test that path_in_fill implicitly closes contours. I think this is wrong,
+ * but it is what "everybody" does.
+ */
+static void
+test_unclosed_in_fill (void)
+{
+ GskPath *path;
+
+ path = gsk_path_parse ("M 0 0 L 0 100 L 100 100 L 100 0 Z");
+ g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
+ gsk_path_unref (path);
+
+ path = gsk_path_parse ("M 0 0 L 0 100 L 100 100 L 100 0");
+ g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
+ gsk_path_unref (path);
+}
+
+static void
+test_rect (void)
+{
+ GskPathBuilder *builder;
+ GskPath *path;
+ GskPathMeasure *measure;
+ GskPathPoint *point;
+ graphene_point_t p;
+ graphene_vec2_t tangent, expected_tangent;
+ float length;
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 50));
+ path = gsk_path_builder_free_to_path (builder);
+ measure = gsk_path_measure_new (path);
+ length = gsk_path_measure_get_length (measure);
+
+ g_assert_true (length == 300);
+
+#define TEST_POS_AT(distance, X, Y) \
+ point = gsk_path_measure_get_point (measure, distance); \
+ gsk_path_point_get_position (point, &p); \
+ g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
+ gsk_path_point_unref (point); \
+ point = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (X, Y), INFINITY); \
+ if (distance < length) \
+ g_assert_true (fabs (gsk_path_measure_get_distance (measure, point) - distance) < 0.01); \
+ else \
+ g_assert_true (fabs (gsk_path_measure_get_distance (measure, point)) < 0.01); \
+ gsk_path_point_get_position (point, &p); \
+ g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
+ gsk_path_point_unref (point);
+
+#define TEST_TANGENT_AT(distance, x1, y1, x2, y2) \
+ point = gsk_path_measure_get_point (measure, distance); \
+ gsk_path_point_get_tangent (point, GSK_PATH_START, &tangent); \
+ g_assert_true (graphene_vec2_near (&tangent, graphene_vec2_init (&expected_tangent, x1, y1), 0.01)); \
+ gsk_path_point_get_tangent (point, GSK_PATH_END, &tangent); \
+ g_assert_true (graphene_vec2_near (&tangent, graphene_vec2_init (&expected_tangent, x2, y2), 0.01)); \
+ gsk_path_point_unref (point); \
+
+#define TEST_POS_AT2(distance, X, Y, expected_distance) \
+ point = gsk_path_measure_get_point (measure, distance); \
+ gsk_path_point_get_position (point, &p); \
+ g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
+ gsk_path_point_unref (point); \
+ point = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (X, Y), INFINITY); \
+ g_assert_true (fabs (gsk_path_measure_get_distance (measure, point) - expected_distance) < 0.01); \
+ gsk_path_point_get_position (point, &p); \
+ g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
+ gsk_path_point_unref (point);
+
+ TEST_POS_AT (0, 0, 0)
+ TEST_POS_AT (25, 25, 0)
+ TEST_POS_AT (100, 100, 0)
+ TEST_POS_AT (110, 100, 10)
+ TEST_POS_AT (150, 100, 50)
+ TEST_POS_AT (175, 75, 50)
+ TEST_POS_AT (250, 0, 50)
+ TEST_POS_AT (260, 0, 40)
+ TEST_POS_AT (300, 0, 0)
+
+ TEST_TANGENT_AT (0, 0, -1, 1, 0)
+ TEST_TANGENT_AT (50, 1, 0, 1, 0)
+ TEST_TANGENT_AT (100, 1, 0, 0, 1)
+ TEST_TANGENT_AT (125, 0, 1, 0, 1)
+ TEST_TANGENT_AT (150, 0, 1, -1, 0)
+ TEST_TANGENT_AT (200, -1, 0, -1, 0)
+ TEST_TANGENT_AT (250, -1, 0, 0, -1)
+ TEST_TANGENT_AT (275, 0, -1, 0, -1)
+
+ gsk_path_measure_unref (measure);
+ gsk_path_unref (path);
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 50, -100, -50));
+ path = gsk_path_builder_free_to_path (builder);
+ measure = gsk_path_measure_new (path);
+ length = gsk_path_measure_get_length (measure);
+
+ g_assert_true (length == 300);
+
+ TEST_POS_AT (0, 100, 50)
+ TEST_POS_AT (25, 75, 50)
+ TEST_POS_AT (100, 0, 50)
+ TEST_POS_AT (110, 0, 40)
+ TEST_POS_AT (150, 0, 0)
+ TEST_POS_AT (175, 25, 0)
+ TEST_POS_AT (250, 100, 0)
+ TEST_POS_AT (260, 100, 10)
+ TEST_POS_AT (300, 100, 50)
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 0, -100, 50));
+ path = gsk_path_builder_free_to_path (builder);
+ measure = gsk_path_measure_new (path);
+ length = gsk_path_measure_get_length (measure);
+
+ g_assert_true (length == 300);
+
+ TEST_POS_AT (0, 100, 0)
+ TEST_POS_AT (25, 75, 0)
+ TEST_POS_AT (100, 0, 0)
+ TEST_POS_AT (110, 0, 10)
+ TEST_POS_AT (150, 0, 50)
+ TEST_POS_AT (175, 25, 50)
+ TEST_POS_AT (250, 100, 50)
+ TEST_POS_AT (260, 100, 40)
+ TEST_POS_AT (300, 100, 0)
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 0));
+ path = gsk_path_builder_free_to_path (builder);
+ measure = gsk_path_measure_new (path);
+ length = gsk_path_measure_get_length (measure);
+
+ g_assert_true (length == 200);
+
+ TEST_POS_AT2 (0, 0, 0, 0)
+ TEST_POS_AT2 (25, 25, 0, 25)
+ TEST_POS_AT2 (100, 100, 0, 100)
+ TEST_POS_AT2 (110, 90, 0, 90)
+ TEST_POS_AT2 (200, 0, 0, 0)
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 0, -100, 0));
+ path = gsk_path_builder_free_to_path (builder);
+ measure = gsk_path_measure_new (path);
+ length = gsk_path_measure_get_length (measure);
+
+ g_assert_true (length == 200);
+
+ TEST_POS_AT2 (0, 100, 0, 0)
+ TEST_POS_AT2 (25, 75, 0, 25)
+ TEST_POS_AT2 (100, 0, 0, 100)
+ TEST_POS_AT2 (110, 10, 0, 90)
+ TEST_POS_AT2 (200, 100, 0, 0)
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 100, 0, -100));
+ path = gsk_path_builder_free_to_path (builder);
+ measure = gsk_path_measure_new (path);
+ length = gsk_path_measure_get_length (measure);
+
+ g_assert_true (length == 200);
+
+ TEST_POS_AT2 (0, 0, 100, 0)
+ TEST_POS_AT2 (25, 0, 75, 25)
+ TEST_POS_AT2 (100, 0, 0, 100)
+ TEST_POS_AT2 (110, 0, 10, 90)
+ TEST_POS_AT2 (200, 0, 100, 0)
+
+#undef TEST_POS_AT
+#undef TEST_POS_AT2
+#undef TEST_TANGENT_AT
+
+ gsk_path_measure_unref (measure);
+ gsk_path_unref (path);
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ gtk_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/measure/bad-split", test_bad_split);
+ g_test_add_func ("/measure/bad-in-fill", test_bad_in_fill);
+ g_test_add_func ("/measure/unclosed-in-fill", test_unclosed_in_fill);
+ g_test_add_func ("/measure/rect", test_rect);
+
+ return g_test_run ();
+}
diff --git a/testsuite/gsk/measure.c b/testsuite/gsk/measure.c
new file mode 100644
index 0000000000..41d34afdc4
--- /dev/null
+++ b/testsuite/gsk/measure.c
@@ -0,0 +1,907 @@
+/*
+ * Copyright © 2020 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#include
+
+static float
+random_weight (void)
+{
+ if (g_test_rand_bit ())
+ return g_test_rand_double_range (1, 20);
+ else
+ return 1.0 / g_test_rand_double_range (1, 20);
+}
+
+static GskPath *
+create_random_degenerate_path (guint max_contours)
+{
+#define N_DEGENERATE_PATHS 15
+ GskPathBuilder *builder;
+ guint i;
+
+ builder = gsk_path_builder_new ();
+
+ switch (g_test_rand_int_range (0, N_DEGENERATE_PATHS))
+ {
+ case 0:
+ /* empty path */
+ break;
+
+ case 1:
+ /* a single point */
+ gsk_path_builder_move_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ break;
+
+ case 2:
+ /* N points */
+ for (i = 0; i < MIN (10, max_contours); i++)
+ {
+ gsk_path_builder_move_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ }
+ break;
+
+ case 3:
+ /* 1 closed point */
+ gsk_path_builder_move_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ gsk_path_builder_close (builder);
+ break;
+
+ case 4:
+ /* the same point closed N times */
+ gsk_path_builder_move_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ for (i = 0; i < MIN (10, max_contours); i++)
+ {
+ gsk_path_builder_close (builder);
+ }
+ break;
+
+ case 5:
+ /* a zero-width and zero-height rect */
+ gsk_path_builder_add_rect (builder,
+ &GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ 0, 0));
+ break;
+
+ case 6:
+ /* a zero-width rect */
+ gsk_path_builder_add_rect (builder,
+ &GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ 0,
+ g_test_rand_double_range (-1000, 1000)));
+ break;
+
+ case 7:
+ /* a zero-height rect */
+ gsk_path_builder_add_rect (builder,
+ &GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ 0));
+ break;
+
+ case 8:
+ /* a negative-size rect */
+ gsk_path_builder_add_rect (builder,
+ &GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 0),
+ g_test_rand_double_range (-1000, 0)));
+ break;
+
+ case 9:
+ /* an absolutely random rect */
+ gsk_path_builder_add_rect (builder,
+ &GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000)));
+ break;
+
+ case 10:
+ /* an absolutely random rect */
+ gsk_path_builder_add_rect (builder,
+ &GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000)));
+ break;
+
+ case 11:
+ /* an absolutely random circle */
+ gsk_path_builder_add_circle (builder,
+ &GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000)),
+ g_test_rand_double_range (1, 1000));
+ break;
+
+ case 12:
+ /* a zero-length line */
+ {
+ graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ gsk_path_builder_move_to (builder, point.x, point.y);
+ gsk_path_builder_line_to (builder, point.x, point.y);
+ }
+ break;
+
+ case 13:
+ /* a curve with start == end */
+ {
+ graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ gsk_path_builder_move_to (builder, point.x, point.y);
+ gsk_path_builder_cubic_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ point.x, point.y);
+ }
+ break;
+
+ case 14:
+ /* a conic with start == end */
+ {
+ graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ gsk_path_builder_move_to (builder, point.x, point.y);
+ gsk_path_builder_conic_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ point.x, point.y,
+ random_weight ());
+ }
+ break;
+
+ case N_DEGENERATE_PATHS:
+ default:
+ g_assert_not_reached ();
+ }
+
+ return gsk_path_builder_free_to_path (builder);
+}
+
+static GskPath *
+create_random_path (guint max_contours);
+
+static void
+add_shape_contour (GskPathBuilder *builder)
+{
+#define N_SHAPE_CONTOURS 3
+ switch (g_test_rand_int_range (0, N_SHAPE_CONTOURS))
+ {
+ case 0:
+ gsk_path_builder_add_rect (builder,
+ &GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (1, 1000),
+ g_test_rand_double_range (1, 1000)));
+ break;
+
+ case 1:
+ gsk_path_builder_add_circle (builder,
+ &GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000)),
+ g_test_rand_double_range (1, 1000));
+ break;
+
+ case 2:
+ {
+ GskPath *path = create_random_path (1);
+ gsk_path_builder_add_path (builder, path);
+ gsk_path_unref (path);
+ }
+ break;
+
+ case N_SHAPE_CONTOURS:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+add_standard_contour (GskPathBuilder *builder)
+{
+ guint i, n;
+
+ if (g_test_rand_bit ())
+ {
+ if (g_test_rand_bit ())
+ gsk_path_builder_move_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ else
+ gsk_path_builder_rel_move_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ }
+
+ /* that 20 is random, but should be enough to get some
+ * crazy self-intersecting shapes */
+ n = g_test_rand_int_range (1, 20);
+ for (i = 0; i < n; i++)
+ {
+ switch (g_test_rand_int_range (0, 8))
+ {
+ case 0:
+ gsk_path_builder_line_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ break;
+
+ case 1:
+ gsk_path_builder_rel_line_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ break;
+
+ case 2:
+ gsk_path_builder_quad_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ break;
+
+ case 3:
+ gsk_path_builder_rel_quad_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ break;
+
+ case 4:
+ gsk_path_builder_cubic_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ break;
+
+ case 5:
+ gsk_path_builder_rel_cubic_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ break;
+
+ case 6:
+ gsk_path_builder_conic_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ random_weight ());
+ break;
+
+ case 7:
+ gsk_path_builder_rel_conic_to (builder,
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000),
+ random_weight ());
+ break;
+
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ }
+
+ if (g_test_rand_bit ())
+ gsk_path_builder_close (builder);
+}
+
+static GskPath *
+create_random_path (guint max_contours)
+{
+ GskPathBuilder *builder;
+ guint i, n;
+
+ /* 5% chance for a weird shape */
+ if (!g_test_rand_int_range (0, 20))
+ return create_random_degenerate_path (max_contours);
+
+ builder = gsk_path_builder_new ();
+ n = g_test_rand_int_range (1, 10);
+ n = MIN (n, max_contours);
+
+ for (i = 0; i < n; i++)
+ {
+ /* 2/3 of shapes are standard contours */
+ if (g_test_rand_int_range (0, 3))
+ add_standard_contour (builder);
+ else
+ add_shape_contour (builder);
+ }
+
+ return gsk_path_builder_free_to_path (builder);
+}
+
+typedef struct {
+ GskPathOperation op;
+ graphene_point_t pts[4];
+ float weight;
+} PathOperation;
+
+static void
+test_segment_start (void)
+{
+ GskPath *path, *path1;
+ GskPathMeasure *measure, *measure1;
+ GskPathBuilder *builder;
+ float epsilon, length;
+ guint i;
+
+ path = create_random_path (G_MAXUINT);
+ measure = gsk_path_measure_new (path);
+ length = gsk_path_measure_get_length (measure);
+ epsilon = MAX (length / 1024, G_MINFLOAT);
+
+ for (i = 0; i < 100; i++)
+ {
+ float seg_length = length * i / 100.0f;
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_segment (builder, measure, 0, seg_length);
+ path1 = gsk_path_builder_free_to_path (builder);
+ measure1 = gsk_path_measure_new (path1);
+
+ if (seg_length == 0)
+ g_assert_cmpfloat_with_epsilon (gsk_path_measure_get_length (measure), gsk_path_measure_get_length (measure1), epsilon);
+ else
+ g_assert_cmpfloat_with_epsilon (seg_length, gsk_path_measure_get_length (measure1), epsilon);
+
+ gsk_path_measure_unref (measure1);
+ gsk_path_unref (path1);
+ }
+
+ gsk_path_measure_unref (measure);
+ gsk_path_unref (path);
+}
+
+static void
+test_segment_end (void)
+{
+ GskPath *path, *path1;
+ GskPathMeasure *measure, *measure1;
+ GskPathBuilder *builder;
+ float epsilon, length;
+ guint i;
+
+ path = create_random_path (G_MAXUINT);
+ measure = gsk_path_measure_new (path);
+ length = gsk_path_measure_get_length (measure);
+ epsilon = MAX (length / 1024, G_MINFLOAT);
+
+ for (i = 0; i < 100; i++)
+ {
+ float seg_length = length * i / 100.0f;
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_segment (builder, measure, length - seg_length, length);
+ path1 = gsk_path_builder_free_to_path (builder);
+ measure1 = gsk_path_measure_new (path1);
+
+ if (seg_length == 0)
+ g_assert_cmpfloat_with_epsilon (gsk_path_measure_get_length (measure), gsk_path_measure_get_length (measure1), epsilon);
+ else
+ g_assert_cmpfloat_with_epsilon (seg_length, gsk_path_measure_get_length (measure1), epsilon);
+
+ gsk_path_measure_unref (measure1);
+ gsk_path_unref (path1);
+ }
+
+ gsk_path_measure_unref (measure);
+ gsk_path_unref (path);
+}
+
+static void
+test_segment_chunk (void)
+{
+ GskPath *path, *path1, *path2;
+ GskPathMeasure *measure, *measure1, *measure2;
+ GskPathBuilder *builder;
+ float epsilon, length;
+ guint i;
+
+ path = create_random_path (G_MAXUINT);
+ measure = gsk_path_measure_new (path);
+ length = gsk_path_measure_get_length (measure);
+ epsilon = MAX (length / 1024, G_MINFLOAT);
+
+ for (i = 0; i <= 100; i++)
+ {
+ float seg_start = length * i / 200.0f;
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_segment (builder, measure, seg_start, seg_start + length / 2);
+ path1 = gsk_path_builder_free_to_path (builder);
+ measure1 = gsk_path_measure_new (path1);
+
+ g_assert_cmpfloat_with_epsilon (length / 2, gsk_path_measure_get_length (measure1), epsilon);
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_segment (builder, measure, seg_start + length / 2, seg_start);
+ path2 = gsk_path_builder_free_to_path (builder);
+ measure2 = gsk_path_measure_new (path2);
+
+ g_assert_cmpfloat_with_epsilon (length / 2, gsk_path_measure_get_length (measure2), epsilon);
+
+ gsk_path_measure_unref (measure2);
+ gsk_path_unref (path2);
+ gsk_path_measure_unref (measure1);
+ gsk_path_unref (path1);
+ }
+
+ gsk_path_measure_unref (measure);
+ gsk_path_unref (path);
+}
+
+static void
+test_segment (void)
+{
+ GskPath *path, *path1, *path2, *path3;
+ GskPathMeasure *measure, *measure1, *measure2, *measure3;
+ GskPathBuilder *builder;
+ guint i;
+ float split1, split2, epsilon, length;
+
+ for (i = 0; i < 1000; i++)
+ {
+ path = create_random_path (G_MAXUINT);
+ measure = gsk_path_measure_new (path);
+ length = gsk_path_measure_get_length (measure);
+ /* chosen high enough to stop the testsuite from failing */
+ epsilon = MAX (length / 64, 1.f / 1024);
+
+ split1 = g_test_rand_double_range (0, length);
+ split2 = g_test_rand_double_range (split1, length);
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_segment (builder, measure, 0, split1);
+ path1 = gsk_path_builder_free_to_path (builder);
+ measure1 = gsk_path_measure_new (path1);
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_segment (builder, measure, split1, split2);
+ path2 = gsk_path_builder_free_to_path (builder);
+ measure2 = gsk_path_measure_new (path2);
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_segment (builder, measure, split2, length);
+ path3 = gsk_path_builder_free_to_path (builder);
+ measure3 = gsk_path_measure_new (path3);
+
+ g_assert_cmpfloat_with_epsilon (split1, gsk_path_measure_get_length (measure1), epsilon);
+ g_assert_cmpfloat_with_epsilon (split2 - split1, gsk_path_measure_get_length (measure2), epsilon);
+ g_assert_cmpfloat_with_epsilon (length - split2, gsk_path_measure_get_length (measure3), epsilon);
+
+ gsk_path_measure_unref (measure2);
+ gsk_path_measure_unref (measure1);
+ gsk_path_measure_unref (measure);
+ gsk_path_unref (path2);
+ gsk_path_unref (path1);
+ gsk_path_unref (path);
+ }
+}
+
+static void
+test_get_point (void)
+{
+ static const guint max_contours = 5;
+ static const float tolerance = 1.0;
+ GskPath *path;
+ GskPathMeasure *measure;
+ GskPathPoint *point;
+ guint n_discontinuities;
+ float length, offset, last_offset;
+ graphene_point_t p, last_point;
+ guint i, j;
+
+ for (i = 0; i < 10; i++)
+ {
+ path = create_random_path (max_contours);
+ measure = gsk_path_measure_new_with_tolerance (path, tolerance);
+ length = gsk_path_measure_get_length (measure);
+ n_discontinuities = 0;
+
+ point = gsk_path_measure_get_point (measure, 0);
+ if (point == NULL)
+ {
+ g_assert_true (gsk_path_is_empty (path));
+ continue;
+ }
+ gsk_path_point_get_position (point, &last_point);
+ gsk_path_point_unref (point);
+
+ /* FIXME: anything we can test with tangents here? */
+ last_offset = 0;
+
+ for (j = 1; j <= 1024; j++)
+ {
+ offset = length * j / 1024;
+ point = gsk_path_measure_get_point (measure, offset);
+ gsk_path_point_get_position (point, &p);
+ gsk_path_point_unref (point);
+
+ if (graphene_point_distance (&last_point, &p, NULL, NULL) > 2 * (offset - last_offset))
+ {
+ n_discontinuities++;
+ g_assert_cmpint (n_discontinuities, <, max_contours);
+ }
+
+ last_offset = offset;
+ last_point = p;
+ }
+
+ gsk_path_measure_unref (measure);
+ gsk_path_unref (path);
+ }
+}
+
+static void
+test_closest_point (void)
+{
+ static const float tolerance = 0.5;
+ GskPath *path, *path1, *path2;
+ GskPathMeasure *measure, *measure1, *measure2;
+ GskPathBuilder *builder;
+ GskPathPoint *point;
+ guint i, j;
+
+ for (i = 0; i < 10; i++)
+ {
+ path1 = create_random_path (G_MAXUINT);
+ measure1 = gsk_path_measure_new_with_tolerance (path1, tolerance);
+ path2 = create_random_path (G_MAXUINT);
+ measure2 = gsk_path_measure_new_with_tolerance (path2, tolerance);
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_path (builder, path1);
+ gsk_path_builder_add_path (builder, path2);
+ path = gsk_path_builder_free_to_path (builder);
+ measure = gsk_path_measure_new_with_tolerance (path, tolerance);
+
+ for (j = 0; j < 100; j++)
+ {
+ graphene_point_t test = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ graphene_point_t p1, p2, p;
+ graphene_vec2_t t1, t2, t;
+ float offset1, offset2, offset;
+ float distance1, distance2, distance;
+
+ offset1 = offset2 = offset = 0;
+ distance1 = distance2 = distance = 0;
+ point = gsk_path_get_closest_point (path1, &test, INFINITY);
+ g_assert_true (point != NULL);
+
+ gsk_path_point_get_position (point, &p1);
+ gsk_path_point_get_tangent (point, GSK_PATH_END, &t1);
+ offset1 = gsk_path_measure_get_distance (measure1, point);
+ distance1 = graphene_point_distance (&p1, &test, NULL, NULL);
+ gsk_path_point_unref (point);
+
+ point = gsk_path_get_closest_point (path2, &test, INFINITY);
+ g_assert_true (point != NULL);
+
+ gsk_path_point_get_position (point, &p2);
+ gsk_path_point_get_tangent (point, GSK_PATH_END, &t2);
+ offset2 = gsk_path_measure_get_distance (measure2, point);
+ distance2 = graphene_point_distance (&p2, &test, NULL, NULL);
+ gsk_path_point_unref (point);
+
+ point = gsk_path_get_closest_point (path, &test, INFINITY);
+ g_assert_true (point != NULL);
+
+ gsk_path_point_get_position (point, &p);
+ gsk_path_point_get_tangent (point, GSK_PATH_END, &t);
+ offset = gsk_path_measure_get_distance (measure, point);
+ distance = graphene_point_distance (&p, &test, NULL, NULL);
+ gsk_path_point_unref (point);
+
+ if (distance1 < distance2 + tolerance)
+ {
+ g_assert_cmpfloat (distance1, ==, distance);
+ g_assert_cmpfloat (p1.x, ==, p.x);
+ g_assert_cmpfloat (p1.y, ==, p.y);
+ g_assert_cmpfloat (offset1, ==, offset);
+ g_assert_true (graphene_vec2_equal (&t1, &t));
+ }
+ else
+ {
+ g_assert_cmpfloat (distance2, ==, distance);
+ g_assert_cmpfloat (p2.x, ==, p.x);
+ g_assert_cmpfloat (p2.y, ==, p.y);
+ g_assert_cmpfloat_with_epsilon (offset2 + gsk_path_measure_get_length (measure1), offset, MAX (G_MINFLOAT, offset / 1024));
+ g_assert_true (graphene_vec2_equal (&t2, &t));
+ }
+ }
+
+ gsk_path_measure_unref (measure2);
+ gsk_path_measure_unref (measure1);
+ gsk_path_measure_unref (measure);
+ gsk_path_unref (path2);
+ gsk_path_unref (path1);
+ gsk_path_unref (path);
+ }
+}
+
+static void
+test_closest_point_for_point (void)
+{
+ static const float tolerance = 0.5;
+ GskPath *path;
+ GskPathMeasure *measure;
+ GskPathPoint *point;
+ float length, offset, closest_offset, distance;
+ graphene_point_t p, closest_point;
+ guint i, j;
+
+ for (i = 0; i < 100; i++)
+ {
+ path = create_random_path (G_MAXUINT);
+ if (gsk_path_is_empty (path))
+ {
+ /* empty paths have no closest point to anything */
+ gsk_path_unref (path);
+ continue;
+ }
+
+ measure = gsk_path_measure_new_with_tolerance (path, tolerance);
+ length = gsk_path_measure_get_length (measure);
+
+ for (j = 0; j < 100; j++)
+ {
+ offset = g_test_rand_double_range (0, length);
+
+ point = gsk_path_measure_get_point (measure, offset);
+ gsk_path_point_get_position (point, &p);
+ gsk_path_point_unref (point);
+ point = gsk_path_get_closest_point (path, &p, tolerance);
+ g_assert_nonnull (point);
+ gsk_path_point_get_position (point, &closest_point);
+ closest_offset = gsk_path_measure_get_distance (measure, point);
+ distance = graphene_point_distance (&p, &closest_point, NULL, NULL);
+ gsk_path_point_unref (point);
+
+ /* should be given due to the TRUE return above, but who knows... */
+ g_assert_cmpfloat (distance, <=, tolerance);
+ g_assert_cmpfloat (graphene_point_distance (&p, &closest_point, NULL, NULL), <=, tolerance);
+ /* can't do == here because points may overlap if we're unlucky */
+ g_assert_cmpfloat (closest_offset, <, offset + tolerance);
+ }
+
+ gsk_path_measure_unref (measure);
+ gsk_path_unref (path);
+ }
+}
+
+#define N_PATHS 3
+static void
+test_in_fill_union (void)
+{
+ GskPath *path;
+ GskPathMeasure *measure, *measures[N_PATHS];
+ GskPathBuilder *builder;
+ guint i, j, k;
+
+ for (i = 0; i < 100; i++)
+ {
+ builder = gsk_path_builder_new ();
+ for (k = 0; k < N_PATHS; k++)
+ {
+ path = create_random_path (G_MAXUINT);
+ measures[k] = gsk_path_measure_new (path);
+ gsk_path_builder_add_path (builder, path);
+ gsk_path_unref (path);
+ }
+ path = gsk_path_builder_free_to_path (builder);
+ measure = gsk_path_measure_new (path);
+ gsk_path_unref (path);
+
+ for (j = 0; j < 100; j++)
+ {
+ graphene_point_t test = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
+ g_test_rand_double_range (-1000, 1000));
+ GskFillRule fill_rule;
+
+ for (fill_rule = GSK_FILL_RULE_WINDING; fill_rule <= GSK_FILL_RULE_EVEN_ODD; fill_rule++)
+ {
+ guint n_in_fill = 0;
+ gboolean in_fill;
+
+ for (k = 0; k < N_PATHS; k++)
+ {
+ if (gsk_path_in_fill (gsk_path_measure_get_path (measures[k]), &test, GSK_FILL_RULE_EVEN_ODD))
+ n_in_fill++;
+ }
+
+ in_fill = gsk_path_in_fill (gsk_path_measure_get_path (measure), &test, GSK_FILL_RULE_EVEN_ODD);
+
+ switch (fill_rule)
+ {
+ case GSK_FILL_RULE_WINDING:
+ if (n_in_fill == 0)
+ g_assert_false (in_fill);
+ else if (n_in_fill == 1)
+ g_assert_true (in_fill);
+ /* else we can't say anything because the winding rule doesn't give enough info */
+ break;
+
+ case GSK_FILL_RULE_EVEN_ODD:
+ g_assert_cmpint (in_fill, ==, n_in_fill & 1);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ }
+
+ gsk_path_measure_unref (measure);
+ for (k = 0; k < N_PATHS; k++)
+ {
+ gsk_path_measure_unref (measures[k]);
+ }
+ }
+}
+#undef N_PATHS
+
+/* This is somewhat sucky because using foreach breaks up the contours
+ * (like rects and circles) and replaces everything with the standard
+ * contour.
+ * But at least it extensively tests the standard contour.
+ */
+static gboolean
+rotate_path_cb (GskPathOperation op,
+ const graphene_point_t *pts,
+ gsize n_pts,
+ float weight,
+ gpointer user_data)
+{
+ GskPathBuilder **builders = user_data;
+
+ switch (op)
+ {
+ case GSK_PATH_MOVE:
+ gsk_path_builder_move_to (builders[0], pts[0].x, pts[0].y);
+ gsk_path_builder_move_to (builders[1], pts[0].y, -pts[0].x);
+ break;
+
+ case GSK_PATH_CLOSE:
+ gsk_path_builder_close (builders[0]);
+ gsk_path_builder_close (builders[1]);
+ break;
+
+ case GSK_PATH_LINE:
+ gsk_path_builder_line_to (builders[0], pts[1].x, pts[1].y);
+ gsk_path_builder_line_to (builders[1], pts[1].y, -pts[1].x);
+ break;
+
+ case GSK_PATH_QUAD:
+ gsk_path_builder_quad_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
+ gsk_path_builder_quad_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
+ break;
+
+ case GSK_PATH_CUBIC:
+ gsk_path_builder_cubic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
+ gsk_path_builder_cubic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, pts[3].y, -pts[3].x);
+ break;
+
+ case GSK_PATH_CONIC:
+ gsk_path_builder_conic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
+ gsk_path_builder_conic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, weight);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+test_in_fill_rotated (void)
+{
+ GskPath *path;
+ GskPathBuilder *builders[2];
+ GskPathMeasure *measures[2];
+ guint i, j;
+
+#define N_FILL_RULES 2
+ /* if this triggers, you added a new enum value to GskFillRule, so the define above needs
+ * an update */
+ g_assert_null (g_enum_get_value (g_type_class_ref (GSK_TYPE_FILL_RULE), N_FILL_RULES));
+
+ for (i = 0; i < 100; i++)
+ {
+ path = create_random_path (G_MAXUINT);
+ builders[0] = gsk_path_builder_new ();
+ builders[1] = gsk_path_builder_new ();
+ /* Use -1 here because we want all the flags, even future additions */
+ gsk_path_foreach (path, -1, rotate_path_cb, builders);
+ gsk_path_unref (path);
+
+ path = gsk_path_builder_free_to_path (builders[0]);
+ measures[0] = gsk_path_measure_new (path);
+ gsk_path_unref (path);
+ path = gsk_path_builder_free_to_path (builders[1]);
+ measures[1] = gsk_path_measure_new (path);
+ gsk_path_unref (path);
+
+ for (j = 0; j < 100; j++)
+ {
+ GskFillRule fill_rule = g_random_int_range (0, N_FILL_RULES);
+ float x = g_test_rand_double_range (-1000, 1000);
+ float y = g_test_rand_double_range (-1000, 1000);
+
+ g_assert_cmpint (gsk_path_in_fill (gsk_path_measure_get_path (measures[0]), &GRAPHENE_POINT_INIT (x, y), fill_rule),
+ ==,
+ gsk_path_in_fill (gsk_path_measure_get_path (measures[1]), &GRAPHENE_POINT_INIT (y, -x), fill_rule));
+ g_assert_cmpint (gsk_path_in_fill (gsk_path_measure_get_path (measures[0]), &GRAPHENE_POINT_INIT (y, x), fill_rule),
+ ==,
+ gsk_path_in_fill (gsk_path_measure_get_path (measures[1]), &GRAPHENE_POINT_INIT (x, -y), fill_rule));
+ }
+
+ gsk_path_measure_unref (measures[0]);
+ gsk_path_measure_unref (measures[1]);
+ }
+#undef N_FILL_RULES
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ gtk_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/measure/segment_start", test_segment_start);
+ g_test_add_func ("/measure/segment_end", test_segment_end);
+ g_test_add_func ("/measure/segment_chunk", test_segment_chunk);
+ g_test_add_func ("/measure/segment", test_segment);
+ g_test_add_func ("/measure/get_point", test_get_point);
+ g_test_add_func ("/measure/closest_point", test_closest_point);
+ g_test_add_func ("/measure/closest_point_for_point", test_closest_point_for_point);
+ g_test_add_func ("/measure/in-fill-union", test_in_fill_union);
+ g_test_add_func ("/measure/in-fill-rotated", test_in_fill_rotated);
+
+ return g_test_run ();
+}
diff --git a/testsuite/gsk/meson.build b/testsuite/gsk/meson.build
index 3e94578a8c..a3b8716f61 100644
--- a/testsuite/gsk/meson.build
+++ b/testsuite/gsk/meson.build
@@ -357,7 +357,9 @@ foreach test : node_parser_tests
endforeach
tests = [
- ['path'],
+ ['measure'],
+ ['measure-special-cases'],
+ ['path'],
['path-special-cases'],
['transform'],
['shader'],