diff --git a/testsuite/gsk/measure-special-cases.c b/testsuite/gsk/measure-special-cases.c new file mode 100644 index 0000000000..6ef3446db9 --- /dev/null +++ b/testsuite/gsk/measure-special-cases.c @@ -0,0 +1,272 @@ +/* + * 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_basics (void) +{ + GskPath *path; + GskPathMeasure *measure; + GskPathPoint point, point1; + float epsilon = 0.001; + float length; + + path = gsk_path_parse ("M 0 0 L 100 0 C 100 100 200 100 200 0"); + measure = gsk_path_measure_new (path); + + gsk_path_measure_get_point (measure, 0, &point); + gsk_path_get_start_point (path, &point1); + g_assert_true (gsk_path_point_equal (&point, &point1)); + + g_assert_cmpfloat_with_epsilon (gsk_path_point_get_distance (measure, &point), 0, epsilon); + + gsk_path_measure_get_point (measure, 50, &point); + g_assert_cmpfloat_with_epsilon (gsk_path_point_get_distance (measure, &point), 50, epsilon); + + gsk_path_measure_get_point (measure, 100, &point); + g_assert_cmpfloat_with_epsilon (gsk_path_point_get_distance (measure, &point), 100, epsilon); + + gsk_path_measure_get_point (measure, 150, &point); + g_assert_cmpfloat_with_epsilon (gsk_path_point_get_distance (measure, &point), 150, epsilon); + + length = gsk_path_measure_get_length (measure); + gsk_path_measure_get_point (measure, length, &point); + gsk_path_get_end_point (path, &point1); + g_assert_true (gsk_path_point_equal (&point, &point1)); + + g_assert_cmpfloat_with_epsilon (gsk_path_point_get_distance (measure, &point), length, epsilon); + + gsk_path_measure_unref (measure); + gsk_path_unref (path); +} + +static void +test_bad_split (void) +{ + GskPath *path, *path1; + GskPathMeasure *measure, *measure1; + GskPathBuilder *builder; + float split, length, epsilon; + GskPathPoint point1, point2; + + /* 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); + + gsk_path_get_start_point (path, &point1); + gsk_path_measure_get_point (measure, split, &point2); + + builder = gsk_path_builder_new (); + gsk_path_builder_add_segment (builder, path, &point1, &point2); + 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_rect (void) +{ + GskPathBuilder *builder; + GskPath *path; + GskPathMeasure *measure; + GskPathPoint point; + graphene_point_t p; + graphene_vec2_t tangent, expected_tangent; + float length; + gboolean ret; + + 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) \ + ret = gsk_path_measure_get_point (measure, distance, &point); \ + g_assert_true (ret); \ + gsk_path_point_get_position (path, &point, &p); \ + g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \ + ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (X, Y), INFINITY, &point); \ + g_assert_true (ret); \ + if (distance < length) \ + g_assert_true (fabs (gsk_path_point_get_distance (measure, &point) - distance) < 0.01); \ + else \ + g_assert_true (fabs (gsk_path_point_get_distance (measure, &point)) < 0.01); \ + gsk_path_point_get_position (path, &point, &p); \ + g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \ + +#define TEST_TANGENT_AT(distance, x1, y1, x2, y2) \ + ret = gsk_path_measure_get_point (measure, distance, &point); \ + g_assert_true (ret); \ + gsk_path_point_get_tangent (path, &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 (path, &point, GSK_PATH_END, &tangent); \ + g_assert_true (graphene_vec2_near (&tangent, graphene_vec2_init (&expected_tangent, x2, y2), 0.01)); \ + +#define TEST_POS_AT2(distance, X, Y, expected_distance) \ + ret = gsk_path_measure_get_point (measure, distance, &point); \ + g_assert_true (ret); \ + gsk_path_point_get_position (path, &point, &p); \ + g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \ + ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (X, Y), INFINITY, &point); \ + g_assert_true (ret); \ + g_assert_true (fabs (gsk_path_point_get_distance (measure, &point) - expected_distance) < 0.01); \ + gsk_path_point_get_position (path, &point, &p); \ + g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \ + + 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_AT2 (300, 0, 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); + + /* These cases are ambiguous */ + 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, 110) + 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); + + /* These cases are ambiguous */ + 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, 110) + 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/basics", test_basics); + g_test_add_func ("/measure/bad-split", test_bad_split); + 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..25bde3326f --- /dev/null +++ b/testsuite/gsk/measure.c @@ -0,0 +1,555 @@ +/* + * 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 GskPath * +create_random_degenerate_path (guint max_contours) +{ +#define N_DEGENERATE_PATHS 14 + 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 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, 6)) + { + 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; + + 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); +} + +static void +test_roundtrip (void) +{ + GskPath *path; + GskPathMeasure *measure; + float length, seg_length; + int i; + GskPathPoint point; + float epsilon = 0.5; + + path = create_random_path (10); + measure = gsk_path_measure_new (path); + length = gsk_path_measure_get_length (measure); + + for (i = 0; i <= 100; i++) + { + seg_length = length * i / 100.0f; + + gsk_path_measure_get_point (measure, seg_length, &point); + + g_assert_cmpfloat_with_epsilon (seg_length, gsk_path_point_get_distance (measure, &point), epsilon); + } + + gsk_path_measure_unref (measure); + 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; + gboolean ret; + + 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; + + ret = gsk_path_measure_get_point (measure, 0, &point); + if (!ret) + { + g_assert_true (gsk_path_is_empty (path)); + continue; + } + gsk_path_point_get_position (path, &point, &last_point); + + /* FIXME: anything we can test with tangents here? */ + last_offset = 0; + + for (j = 1; j <= 1024; j++) + { + offset = length * j / 1024; + ret = gsk_path_measure_get_point (measure, offset, &point); + g_assert_true (ret); + gsk_path_point_get_position (path, &point, &p); + + 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; + gboolean ret; + + if (!g_test_slow ()) + { + g_test_skip ("Skipping slow test"); + return; + } + + 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; + ret = gsk_path_get_closest_point (path1, &test, INFINITY, &point); + g_assert_true (ret); + + gsk_path_point_get_position (path1, &point, &p1); + gsk_path_point_get_tangent (path1, &point, GSK_PATH_END, &t1); + offset1 = gsk_path_point_get_distance (measure1, &point); + distance1 = graphene_point_distance (&p1, &test, NULL, NULL); + + ret = gsk_path_get_closest_point (path2, &test, INFINITY, &point); + g_assert_true (ret); + + gsk_path_point_get_position (path2, &point, &p2); + gsk_path_point_get_tangent (path2, &point, GSK_PATH_END, &t2); + offset2 = gsk_path_point_get_distance (measure2, &point); + distance2 = graphene_point_distance (&p2, &test, NULL, NULL); + + ret = gsk_path_get_closest_point (path, &test, INFINITY, &point); + g_assert_true (ret); + + gsk_path_point_get_position (path, &point, &p); + gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &t); + offset = gsk_path_point_get_distance (measure, &point); + distance = graphene_point_distance (&p, &test, NULL, NULL); + + if (distance1 == distance) + { + 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, distance; + graphene_point_t p, closest_point; + guint i, j; + gboolean ret; + + if (!g_test_slow ()) + { + g_test_skip ("Skipping slow test"); + return; + } + + 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); + ret = gsk_path_measure_get_point (measure, offset, &point); + g_assert_true (ret); + gsk_path_point_get_position (path, &point, &p); + ret = gsk_path_get_closest_point (path, &p, 2 * tolerance, &point); + g_assert_true (ret); + gsk_path_point_get_position (path, &point, &closest_point); + //closest_offset = gsk_path_point_get_distance (measure, &point); + distance = graphene_point_distance (&p, &closest_point, NULL, NULL); + + /* should be given due to the TRUE return above, but who knows... */ + g_assert_cmpfloat (distance, <=, 2 * tolerance); + g_assert_cmpfloat (graphene_point_distance (&p, &closest_point, NULL, NULL), <=, 2 * tolerance); + /* we can't check offsets here, since we might hit self-intersections + g_assert_cmpfloat_with_epsilon (closest_offset, offset, 2 * tolerance); + */ + } + + 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/roundtrip", test_roundtrip); + 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); + + return g_test_run (); +} diff --git a/testsuite/gsk/meson.build b/testsuite/gsk/meson.build index 01c0ec24d5..ae502baa39 100644 --- a/testsuite/gsk/meson.build +++ b/testsuite/gsk/meson.build @@ -371,6 +371,8 @@ tests = [ ['shader'], ['path'], ['path-special-cases'], + ['measure'], + ['measure-special-cases'], ] test_cargs = []