/* * Copyright (C) 2024 Red Hat Inc. * * Author: * Matthias Clasen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library. If not, see . */ #include "config.h" #include #include "gtk/gtkcssvalueprivate.h" #include "gtk/gtkcssnumbervalueprivate.h" #include "gtk/gtkcsscolorvalueprivate.h" #include "gtk/css/gtkcssparserprivate.h" #include "gtk/gtkwidgetprivate.h" #include "gtk/gtkcssnodeprivate.h" static GtkWidget *dummy; typedef struct { const char *input; gboolean is_computed; const char *specified; const char *computed; double expected_value; } CssNumberValueTest; static CssNumberValueTest number_tests[] = { { "calc(10 + 2)", TRUE, "12", "12", 12 }, { "calc(10% + 2%)", FALSE, "12%", "12%", 12 }, { "calc(10% + 2px + 2%)", FALSE, "calc(2px + 12%)", "calc(2px + 12%)", 14 }, { "calc(32mm + 2px)", FALSE, "calc(32mm + 2px)", "calc(120.94488188976378px + 2px)", 32 * 96 * 0.039370078740157477 + 2, }, { "calc(32deg * 3 + 0.1turn)", TRUE, "132deg", "132deg", 132 }, { "calc(1s + 500ms)", TRUE, "1.5s", "1.5s", 1.5 }, { "10", TRUE, "10", "10", 10 }, { "calc(2 + 3 + pi)", TRUE, "8.1415926535897931", "8.1415926535897931", 5 + G_PI }, { "calc(2 * 3 * pi)", TRUE, "18.849555921538759", "18.849555921538759", 6 * G_PI }, { "calc(2mm + 3cm)", FALSE, "32mm", "120.94488188976378px", 32 * 96 * 0.039370078740157477, }, { "sin(2 * pi)", TRUE, NULL, NULL, 0 }, { "10%", FALSE, "10%", "10%", 10 }, }; G_GNUC_NORETURN static void error_func (GtkCssParser *parser, const GtkCssLocation *start, const GtkCssLocation *end, const GError *error, gpointer user_data) { g_assert_not_reached (); } static void test_number_value (gconstpointer data) { CssNumberValueTest *test = (CssNumberValueTest *) data; GtkCssValue *value; GtkCssComputeContext context; GtkCssValue *res; GtkCssParser *parser; GBytes *bytes; GtkCssNode *node; if (g_test_verbose ()) g_test_message ("input: %s", test->input); node = gtk_widget_get_css_node (dummy); context.provider = gtk_css_node_get_style_provider (node); context.style = gtk_css_node_get_style (node); context.parent_style = NULL; context.variables = NULL; context.shorthands = NULL; bytes = g_bytes_new_static (test->input, strlen (test->input)); parser = gtk_css_parser_new_for_bytes (bytes, NULL, error_func, NULL, NULL); value = gtk_css_number_value_parse (parser, GTK_CSS_PARSE_PERCENT | GTK_CSS_PARSE_NUMBER | GTK_CSS_PARSE_LENGTH | GTK_CSS_PARSE_ANGLE | GTK_CSS_PARSE_TIME); g_assert_true (gtk_css_value_is_computed (value) == test->is_computed); if (test->specified) { char *str = gtk_css_value_to_string (value); g_assert_cmpstr (str, ==, test->specified); g_free (str); } res = gtk_css_value_compute (value, GTK_CSS_PROPERTY_LETTER_SPACING, &context); if (test->is_computed) g_assert_true (res == value); g_assert_true (gtk_css_number_value_has_percent (res) || gtk_css_value_is_computed (res)); if (test->computed) { char *str = gtk_css_value_to_string (res); g_assert_cmpstr (str, ==, test->computed); g_free (str); } g_assert_cmpfloat_with_epsilon (gtk_css_number_value_get_canonical (res, 100), test->expected_value, FLT_EPSILON); gtk_css_value_unref (value); gtk_css_value_unref (res); gtk_css_parser_unref (parser); g_bytes_unref (bytes); } typedef struct { const char *input; gboolean is_computed; gboolean contains_current_color; const char *specified; const char *computed; const char *current; const char *used; } CssColorValueTest; static CssColorValueTest color_tests[] = { { "rgba(255, 255, 128, 0.1)", TRUE, FALSE, "rgba(255,255,128,0.1)", "rgba(255,255,128,0.1)" }, { "currentcolor", TRUE, TRUE, "currentcolor", "currentcolor", "color(srgb 1 0 0)", "color(srgb 1 0 0)" }, { "color(from color(srgb 0.5 0.5 0.2) srgb 0.5 calc(r * g) b / calc(alpha / 2))", TRUE, FALSE, "color(srgb 0.5 0.25 0.2 / 0.5)", "color(srgb 0.5 0.25 0.2 / 0.5)" }, { "rgb(from currentcolor r g 40% / 50%)", TRUE, TRUE, "color(from currentcolor srgb r g 40% / 50%)", "color(from currentcolor srgb r g 40% / 50%)", "color(srgb 1 0 0)", "color(srgb 1 0 0.4 / 0.5)" }, { "rgb(from darkgoldenrod r g 100 / 50%)", TRUE, FALSE, "color(srgb 0.721569 0.52549 0.392157 / 0.5)", "color(srgb 0.721569 0.52549 0.392157 / 0.5)" }, { "rgb(from white 100% 100% 100% / 100%)", TRUE, FALSE, "color(srgb 1 1 1)", "color(srgb 1 1 1)" }, { "color(from white srgb 100% 100% 100% / 100%)", TRUE, FALSE, "color(srgb 1 1 1)", "color(srgb 1 1 1)" }, }; static void test_color_value (gconstpointer data) { CssColorValueTest *test = (CssColorValueTest *) data; GtkCssValue *value; GtkCssComputeContext context; GtkCssValue *res; GtkCssParser *parser; GBytes *bytes; GtkCssNode *node; if (g_test_verbose ()) g_test_message ("input: %s", test->input); node = gtk_widget_get_css_node (dummy); context.provider = gtk_css_node_get_style_provider (node); context.style = gtk_css_node_get_style (node); context.parent_style = NULL; context.variables = NULL; context.shorthands = NULL; bytes = g_bytes_new_static (test->input, strlen (test->input)); parser = gtk_css_parser_new_for_bytes (bytes, NULL, error_func, NULL, NULL); value = gtk_css_color_value_parse (parser); g_assert_true (gtk_css_value_is_computed (value) == test->is_computed); g_assert_true (gtk_css_value_contains_current_color (value) == test->contains_current_color); if (test->specified) { char *str = gtk_css_value_to_string (value); g_assert_cmpstr (str, ==, test->specified); g_free (str); } res = gtk_css_value_compute (value, GTK_CSS_PROPERTY_COLOR, &context); if (test->is_computed) g_assert_true (res == value); g_assert_true (gtk_css_value_is_computed (res)); g_assert_true (gtk_css_value_contains_current_color (res) == test->contains_current_color); if (test->computed) { char *str = gtk_css_value_to_string (res); g_assert_cmpstr (str, ==, test->computed); g_free (str); } gtk_css_parser_unref (parser); g_bytes_unref (bytes); if (gtk_css_value_contains_current_color (res)) { GtkCssValue *current; GtkCssValue *used; bytes = g_bytes_new_static (test->current, strlen (test->current)); parser = gtk_css_parser_new_for_bytes (bytes, NULL, error_func, NULL, NULL); current = gtk_css_color_value_parse (parser); used = gtk_css_value_resolve (res, &context, current); g_assert_true (gtk_css_value_is_computed (used)); g_assert_false (gtk_css_value_contains_current_color (used)); if (test->used) { char *str = gtk_css_value_to_string (used); g_assert_cmpstr (str, ==, test->used); g_free (str); } gtk_css_parser_unref (parser); g_bytes_unref (bytes); } gtk_css_value_unref (value); gtk_css_value_unref (res); } int main (int argc, char **argv) { gtk_test_init (&argc, &argv); dummy = gtk_window_new (); gtk_widget_realize (dummy); for (int i = 0; i < G_N_ELEMENTS (number_tests); i++) { char *path; path = g_strdup_printf ("/css/compute/number/%d", i); g_test_add_data_func (path, &number_tests[i], test_number_value); g_free (path); } for (int i = 0; i < G_N_ELEMENTS (color_tests); i++) { char *path; path = g_strdup_printf ("/css/compute/color/%d", i); g_test_add_data_func (path, &color_tests[i], test_color_value); g_free (path); } return g_test_run (); }