/* * 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 #include "path-utils.h" typedef struct { GskPathOperation op; graphene_point_t pts[4]; } PathOperation; static void _g_string_append_double (GString *string, double d) { char buf[G_ASCII_DTOSTR_BUF_SIZE]; g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, d); g_string_append (string, buf); } static void _g_string_append_point (GString *string, const graphene_point_t *pt) { _g_string_append_double (string, pt->x); g_string_append_c (string, ' '); _g_string_append_double (string, pt->y); } static void path_operation_print (const PathOperation *p, GString *string) { switch (p->op) { case GSK_PATH_MOVE: g_string_append (string, "M "); _g_string_append_point (string, &p->pts[0]); break; case GSK_PATH_CLOSE: g_string_append (string, " Z"); break; case GSK_PATH_LINE: g_string_append (string, " L "); _g_string_append_point (string, &p->pts[1]); break; case GSK_PATH_QUAD: g_string_append (string, " Q "); _g_string_append_point (string, &p->pts[1]); g_string_append (string, ", "); _g_string_append_point (string, &p->pts[2]); break; case GSK_PATH_CUBIC: g_string_append (string, " C "); _g_string_append_point (string, &p->pts[1]); g_string_append (string, ", "); _g_string_append_point (string, &p->pts[2]); g_string_append (string, ", "); _g_string_append_point (string, &p->pts[3]); break; case GSK_PATH_CONIC: g_string_append (string, " O "); _g_string_append_point (string, &p->pts[1]); g_string_append (string, ", "); _g_string_append_point (string, &p->pts[3]); g_string_append (string, ", "); _g_string_append_double (string, p->pts[2].x); break; default: g_assert_not_reached(); return; } } static gboolean path_operation_equal (const PathOperation *p1, const PathOperation *p2, float epsilon) { if (p1->op != p2->op) return FALSE; /* No need to compare pts[0] for most ops, that's just * duplicate work. */ switch (p1->op) { case GSK_PATH_MOVE: return graphene_point_near (&p1->pts[0], &p2->pts[0], epsilon); case GSK_PATH_LINE: case GSK_PATH_CLOSE: return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon); case GSK_PATH_QUAD: return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon) && graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon); case GSK_PATH_CUBIC: return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon) && graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon) && graphene_point_near (&p1->pts[3], &p2->pts[3], epsilon); case GSK_PATH_CONIC: return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon) && graphene_point_near (&p1->pts[3], &p2->pts[3], epsilon); default: g_return_val_if_reached (FALSE); } } static gboolean collect_path_operation_cb (GskPathOperation op, const graphene_point_t *pts, gsize n_pts, float weight, gpointer user_data) { g_array_append_vals (user_data, (PathOperation[1]) { { op, { GRAPHENE_POINT_INIT(pts[0].x, pts[0].y), GRAPHENE_POINT_INIT(n_pts > 1 ? pts[1].x : 0, n_pts > 1 ? pts[1].y : 0), GRAPHENE_POINT_INIT(n_pts > 2 ? pts[2].x : 0, n_pts > 2 ? pts[2].y : 0), GRAPHENE_POINT_INIT(n_pts > 3 ? pts[3].x : 0, n_pts > 3 ? pts[3].y : 0) }, } }, 1); return TRUE; } static GArray * collect_path (GskPath *path) { GArray *array = g_array_new (FALSE, FALSE, sizeof (PathOperation)); /* Use -1 here because we want all the flags, even future additions */ gsk_path_foreach (path, -1, collect_path_operation_cb, array); return array; } void assert_path_equal_func (const char *domain, const char *file, int line, const char *func, GskPath *path1, GskPath *path2, float epsilon) { GArray *ops1, *ops2; guint i; ops1 = collect_path (path1); ops2 = collect_path (path2); for (i = 0; i < MAX (ops1->len, ops2->len); i++) { PathOperation *op1 = i < ops1->len ? &g_array_index (ops1, PathOperation, i) : NULL; PathOperation *op2 = i < ops2->len ? &g_array_index (ops2, PathOperation, i) : NULL; if (op1 == NULL || op2 == NULL || !path_operation_equal (op1, op2, epsilon)) { GString *string; guint j; /* Find the operation we start to print */ for (j = i; j-- > 0; ) { PathOperation *op = &g_array_index (ops1, PathOperation, j); if (op->op == GSK_PATH_MOVE) break; if (j + 3 == i) { j = i - 1; break; } } string = g_string_new (j == 0 ? "" : "... "); for (; j < i; j++) { PathOperation *op = &g_array_index (ops1, PathOperation, j); path_operation_print (op, string); g_string_append_c (string, ' '); } g_string_append (string, "\\\n "); if (op1) { path_operation_print (op1, string); if (ops1->len > i + 1) g_string_append (string, " ..."); } g_string_append (string, "\n "); if (op1) { path_operation_print (op2, string); if (ops2->len > i + 1) g_string_append (string, " ..."); } g_assertion_message (domain, file, line, func, string->str); g_string_free (string, TRUE); } } g_array_free (ops1, TRUE); g_array_free (ops2, TRUE); }