diff --git a/gtk/gtkcsscalcvalue.c b/gtk/gtkcsscalcvalue.c index fed2fd645d..ba5d1d0c52 100644 --- a/gtk/gtkcsscalcvalue.c +++ b/gtk/gtkcsscalcvalue.c @@ -232,3 +232,307 @@ gtk_css_calc_value_parse (GtkCssParser *parser, return data.value; } +typedef struct +{ + GtkCssNumberParseFlags flags; + GPtrArray *values; +} ParseArgnData; + +static guint +gtk_css_argn_value_parse_arg (GtkCssParser *parser, + guint arg, + gpointer data_) +{ + ParseArgnData *data = data_; + GtkCssValue *value; + + value = gtk_css_calc_value_parse_sum (parser, data->flags); + if (value == NULL) + return 0; + + g_ptr_array_add (data->values, value); + + return 1; +} + +typedef struct +{ + GtkCssNumberParseFlags flags; + GtkCssValue *values[3]; +} ParseClampData; + +static guint +gtk_css_clamp_value_parse_arg (GtkCssParser *parser, + guint arg, + gpointer data_) +{ + ParseClampData *data = data_; + + if ((arg == 0 || arg == 2)) + { + if (gtk_css_parser_try_ident (parser, "none")) + { + data->values[arg] = NULL; + return 1; + } + } + + data->values[arg] = gtk_css_calc_value_parse_sum (parser, data->flags); + if (data->values[arg] == NULL) + return 0; + + return 1; +} + +GtkCssValue * +gtk_css_clamp_value_parse (GtkCssParser *parser, + GtkCssNumberParseFlags flags, + guint type) +{ + ParseClampData data; + GtkCssValue *result = NULL; + + if (!gtk_css_parser_has_function (parser, "clamp")) + { + gtk_css_parser_error_syntax (parser, "Expected 'clamp('"); + return NULL; + } + + /* This can only be handled at compute time, we allow '-' after all */ + data.flags = flags & ~GTK_CSS_POSITIVE_ONLY; + data.values[0] = NULL; + data.values[1] = NULL; + data.values[2] = NULL; + + if (gtk_css_parser_consume_function (parser, 3, 3, gtk_css_clamp_value_parse_arg, &data)) + { + GtkCssDimension dim = gtk_css_number_value_get_dimension (data.values[1]); + if ((data.values[0] && gtk_css_number_value_get_dimension (data.values[0]) != dim) || + (data.values[2] && gtk_css_number_value_get_dimension (data.values[2]) != dim)) + gtk_css_parser_error_syntax (parser, "Inconsistent types in 'clamp('"); + else + result = gtk_css_math_value_new (type, 0, data.values, 3); + } + + if (result == NULL) + { + g_clear_pointer (&data.values[0], gtk_css_value_unref); + g_clear_pointer (&data.values[1], gtk_css_value_unref); + g_clear_pointer (&data.values[2], gtk_css_value_unref); + } + + return result; +} + +typedef struct { + GtkCssNumberParseFlags flags; + guint mode; + gboolean has_mode; + GtkCssValue *values[2]; +} ParseRoundData; + +static guint +gtk_css_round_value_parse_arg (GtkCssParser *parser, + guint arg, + gpointer data_) +{ + ParseRoundData *data = data_; + + if (arg == 0) + { + const char *modes[] = { "nearest", "up", "down", "to-zero" }; + + for (guint i = 0; i < G_N_ELEMENTS (modes); i++) + { + if (gtk_css_parser_try_ident (parser, modes[i])) + { + data->mode = i; + data->has_mode = TRUE; + return 1; + } + } + + data->values[0] = gtk_css_calc_value_parse_sum (parser, data->flags); + if (data->values[0] == NULL) + return 0; + } + else if (arg == 1) + { + GtkCssValue *value = gtk_css_calc_value_parse_sum (parser, data->flags); + + if (value == NULL) + return 0; + + if (data->has_mode) + data->values[0] = value; + else + data->values[1] = value; + } + else + { + if (!data->has_mode) + { + gtk_css_parser_error_syntax (parser, "Too many argument for 'round'"); + return 0; + } + + data->values[1] = gtk_css_calc_value_parse_sum (parser, data->flags); + + if (data->values[1] == NULL) + return 0; + } + + return 1; +} + +GtkCssValue * +gtk_css_round_value_parse (GtkCssParser *parser, + GtkCssNumberParseFlags flags, + guint type) +{ + ParseRoundData data; + GtkCssValue *result = NULL; + + if (!gtk_css_parser_has_function (parser, "round")) + { + gtk_css_parser_error_syntax (parser, "Expected 'round('"); + return NULL; + } + + data.flags = flags & ~GTK_CSS_POSITIVE_ONLY; + data.mode = ROUND_NEAREST; + data.has_mode = FALSE; + data.values[0] = NULL; + data.values[1] = NULL; + + if (gtk_css_parser_consume_function (parser, 1, 3, gtk_css_round_value_parse_arg, &data) && + data.values[0] != NULL) + { + if (data.values[1] != NULL && + gtk_css_number_value_get_dimension (data.values[0]) != + gtk_css_number_value_get_dimension (data.values[1])) + gtk_css_parser_error_syntax (parser, "Inconsistent types in 'round('"); + else if (data.values[1] == NULL && + gtk_css_number_value_get_dimension (data.values[0]) != GTK_CSS_DIMENSION_NUMBER) + gtk_css_parser_error_syntax (parser, "Can't omit second argument to 'round(' here"); + else + result = gtk_css_math_value_new (type, data.mode, data.values, data.values[1] != NULL ? 2 : 1); + } + + if (result == NULL) + { + g_clear_pointer (&data.values[0], gtk_css_value_unref); + g_clear_pointer (&data.values[1], gtk_css_value_unref); + } + + return result; +} + +typedef struct { + GtkCssNumberParseFlags flags; + GtkCssValue *values[2]; +} ParseArg2Data; + +static guint +gtk_css_arg2_value_parse_arg (GtkCssParser *parser, + guint arg, + gpointer data_) +{ + ParseArg2Data *data = data_; + + data->values[arg] = gtk_css_calc_value_parse_sum (parser, data->flags); + if (data->values[arg] == NULL) + return 0; + + return 1; +} + +GtkCssValue * +gtk_css_arg2_value_parse (GtkCssParser *parser, + GtkCssNumberParseFlags flags, + guint min_args, + guint max_args, + const char *function, + guint type) +{ + ParseArg2Data data; + GtkCssValue *result = NULL; + + g_assert (1 <= min_args && min_args <= max_args && max_args <= 2); + + if (!gtk_css_parser_has_function (parser, function)) + { + gtk_css_parser_error_syntax (parser, "Expected '%s('", function); + return NULL; + } + + data.flags = flags & ~GTK_CSS_POSITIVE_ONLY; + data.values[0] = NULL; + data.values[1] = NULL; + + if (gtk_css_parser_consume_function (parser, min_args, max_args, gtk_css_arg2_value_parse_arg, &data)) + { + if (data.values[1] != NULL && + gtk_css_number_value_get_dimension (data.values[0]) != + gtk_css_number_value_get_dimension (data.values[1])) + gtk_css_parser_error_syntax (parser, "Inconsistent types in '%s('", function); + else + result = gtk_css_math_value_new (type, 0, data.values, data.values[1] != NULL ? 2 : 1); + } + + if (result == NULL) + { + g_clear_pointer (&data.values[0], gtk_css_value_unref); + g_clear_pointer (&data.values[1], gtk_css_value_unref); + } + + return result; +} + +GtkCssValue * +gtk_css_argn_value_parse (GtkCssParser *parser, + GtkCssNumberParseFlags flags, + const char *function, + guint type) +{ + ParseArgnData data; + GtkCssValue *result = NULL; + + if (!gtk_css_parser_has_function (parser, function)) + { + gtk_css_parser_error_syntax (parser, "Expected '%s('", function); + return NULL; + } + + /* This can only be handled at compute time, we allow '-' after all */ + data.flags = flags & ~GTK_CSS_POSITIVE_ONLY; + data.values = g_ptr_array_new (); + + if (gtk_css_parser_consume_function (parser, 1, G_MAXUINT, gtk_css_argn_value_parse_arg, &data)) + { + GtkCssValue *val = (GtkCssValue *) g_ptr_array_index (data.values, 0); + GtkCssDimension dim = gtk_css_number_value_get_dimension (val); + guint i; + for (i = 1; i < data.values->len; i++) + { + val = (GtkCssValue *) g_ptr_array_index (data.values, i); + if (gtk_css_number_value_get_dimension (val) != dim) + break; + } + if (i < data.values->len) + gtk_css_parser_error_syntax (parser, "Inconsistent types in '%s('", function); + else + result = gtk_css_math_value_new (type, 0, (GtkCssValue **)data.values->pdata, data.values->len); + } + + if (result == NULL) + { + for (guint i = 0; i < data.values->len; i++) + gtk_css_value_unref ((GtkCssValue *)g_ptr_array_index (data.values, i)); + } + + g_ptr_array_unref (data.values); + + return result; +} + diff --git a/gtk/gtkcsscalcvalueprivate.h b/gtk/gtkcsscalcvalueprivate.h index aa46b6a2a1..acede46387 100644 --- a/gtk/gtkcsscalcvalueprivate.h +++ b/gtk/gtkcsscalcvalueprivate.h @@ -23,6 +23,21 @@ G_BEGIN_DECLS GtkCssValue * gtk_css_calc_value_parse (GtkCssParser *parser, GtkCssNumberParseFlags flags); +GtkCssValue * gtk_css_clamp_value_parse (GtkCssParser *parser, + GtkCssNumberParseFlags flags, + guint type); +GtkCssValue * gtk_css_round_value_parse (GtkCssParser *parser, + GtkCssNumberParseFlags flags, + guint type); +GtkCssValue * gtk_css_arg2_value_parse (GtkCssParser *parser, + GtkCssNumberParseFlags flags, + guint min_args, + guint max_args, + const char *function, + guint type); +GtkCssValue * gtk_css_argn_value_parse (GtkCssParser *parser, + GtkCssNumberParseFlags flags, + const char *function, + guint type); G_END_DECLS - diff --git a/gtk/gtkcssnumbervalue.c b/gtk/gtkcssnumbervalue.c index e2742f47a1..2fc36baf50 100644 --- a/gtk/gtkcssnumbervalue.c +++ b/gtk/gtkcssnumbervalue.c @@ -25,60 +25,128 @@ #include "gtkcssstyleprivate.h" #include "gtkprivate.h" -static GtkCssValue * gtk_css_calc_value_new (guint n_terms); -static GtkCssValue * gtk_css_calc_value_new_sum (GtkCssValue *a, - GtkCssValue *b); -static gsize gtk_css_value_calc_get_size (gsize n_terms); +#include -enum { - TYPE_CALC = 0, - TYPE_DIMENSION = 1, -}; +#define RAD_TO_DEG(x) ((x) * 180.0 / G_PI) +#define DEG_TO_RAD(x) ((x) * G_PI / 180.0) + +static GtkCssValue * gtk_css_calc_value_alloc (guint n_terms); +static GtkCssValue * gtk_css_calc_value_new_sum (GtkCssValue *a, + GtkCssValue *b); +static GtkCssValue * gtk_css_round_value_new (guint mode, + GtkCssValue *a, + GtkCssValue *b); +static GtkCssValue * gtk_css_clamp_value_new (GtkCssValue *min, + GtkCssValue *center, + GtkCssValue *max); + +static double _round (guint mode, double a, double b); +static double _mod (double a, double b); +static double _rem (double a, double b); +static double _sign (double a); + +typedef enum { + TYPE_CALC, + TYPE_DIMENSION, + TYPE_MIN, + TYPE_MAX, + TYPE_CLAMP, + TYPE_ROUND, + TYPE_MOD, + TYPE_REM, + TYPE_PRODUCT, + TYPE_ABS, + TYPE_SIGN, + TYPE_SIN, + TYPE_COS, + TYPE_TAN, + TYPE_ASIN, + TYPE_ACOS, + TYPE_ATAN, + TYPE_ATAN2, + TYPE_POW, + TYPE_SQRT, + TYPE_EXP, + TYPE_LOG, + TYPE_HYPOT, +} NumberValueType; + +static const char *function_name[] = { + "calc", + "", /* TYPE_DIMENSION */ + "min", + "max", + "clamp", + "round", + "mod", + "rem", + "", /* TYPE_PRODUCT */ + "abs", + "sign", + "sin", + "cos", + "tan", + "asin", + "acos", + "atan", + "atan2", + "pow", + "sqrt", + "exp", + "log", + "hypot", + }; struct _GtkCssValue { GTK_CSS_VALUE_BASE - guint type : 1; /* Calc or dimension */ + guint type : 16; union { struct { GtkCssUnit unit; double value; } dimension; struct { + guint mode; guint n_terms; GtkCssValue *terms[1]; } calc; }; }; - static GtkCssValue * -gtk_css_calc_value_new_from_array (GtkCssValue **values, - guint n_values) +gtk_css_calc_value_new (guint type, + guint mode, + GtkCssValue **values, + guint n_values) { GtkCssValue *result; - if (n_values > 1) + if (n_values == 1 && + (type == TYPE_CALC || type == TYPE_PRODUCT || + type == TYPE_MIN || type == TYPE_MAX)) { - result = gtk_css_calc_value_new (n_values); - memcpy (result->calc.terms, values, n_values * sizeof (GtkCssValue *)); - } - else - { - result = values[0]; + return values[0]; } + result = gtk_css_calc_value_alloc (n_values); + result->type = type; + result->calc.mode = mode; + + memcpy (result->calc.terms, values, n_values * sizeof (GtkCssValue *)); + return result; } static void gtk_css_value_number_free (GtkCssValue *number) { - if (number->type == TYPE_CALC) + if (number->type != TYPE_DIMENSION) { - const guint n_terms = number->calc.n_terms; - - for (guint i = 0; i < n_terms; i++) - gtk_css_value_unref (number->calc.terms[i]); + for (guint i = 0; i < number->calc.n_terms; i++) + { + if (number->calc.terms[i]) + gtk_css_value_unref (number->calc.terms[i]); + } } g_free (number); @@ -107,6 +175,132 @@ get_base_font_size_px (guint property_id, return _gtk_css_number_value_get (style->core->font_size, 100); } +/* Canonical units that can be used before compute time + * + * Our compatibility is a bit stricter than CSS, since we + * have a dpi property, so PX and the dpi-dependent units + * can't be unified before compute time. + */ +static GtkCssUnit +canonical_unit (GtkCssUnit unit) +{ + switch (unit) + { + case GTK_CSS_NUMBER: return GTK_CSS_NUMBER; + case GTK_CSS_PERCENT: return GTK_CSS_PERCENT; + case GTK_CSS_PX: return GTK_CSS_PX; + case GTK_CSS_EM: return GTK_CSS_EM; + case GTK_CSS_EX: return GTK_CSS_EM; + case GTK_CSS_REM: return GTK_CSS_REM; + case GTK_CSS_PT: return GTK_CSS_MM; + case GTK_CSS_PC: return GTK_CSS_MM; + case GTK_CSS_IN: return GTK_CSS_MM; + case GTK_CSS_CM: return GTK_CSS_MM; + case GTK_CSS_MM: return GTK_CSS_MM; + case GTK_CSS_RAD: return GTK_CSS_DEG; + case GTK_CSS_DEG: return GTK_CSS_DEG; + case GTK_CSS_GRAD: return GTK_CSS_DEG; + case GTK_CSS_TURN: return GTK_CSS_DEG; + case GTK_CSS_S: return GTK_CSS_S; + case GTK_CSS_MS: return GTK_CSS_S; + default: g_assert_not_reached (); + } +} + +static inline gboolean +unit_is_compute_time (GtkCssUnit unit) +{ + return gtk_css_unit_get_dimension (unit) == GTK_CSS_DIMENSION_LENGTH && + unit != GTK_CSS_PX; +} + +static inline gboolean +value_is_compute_time (GtkCssValue *value) +{ + if (value->type == TYPE_DIMENSION) + return unit_is_compute_time (value->dimension.unit); + + return FALSE; +} + +/* Units are compatible if they have the same compute time + * dependency. This still allows some operations to applied + * early. + */ +static gboolean +units_compatible (GtkCssValue *v1, + GtkCssValue *v2) +{ + if ((v1 && v1->type != TYPE_DIMENSION) || (v2 && v2->type != TYPE_DIMENSION)) + return FALSE; + + if (v1 && v2) + return canonical_unit (v1->dimension.unit) == canonical_unit (v2->dimension.unit); + + return TRUE; +} + +/* Assumes that value is a dimension value and + * unit is canonical and compatible its unit. + */ +static double +get_converted_value (GtkCssValue *value, + GtkCssUnit unit) +{ + double v; + + if (value->type != TYPE_DIMENSION) + return NAN; + + v = _gtk_css_number_value_get (value, 100); + + if (unit == value->dimension.unit) + { + return v; + } + else if (unit == GTK_CSS_MM) + { + switch ((int) value->dimension.unit) + { + case GTK_CSS_PT: return v * 0.35277778; + case GTK_CSS_PC: return v * 4.2333333; + case GTK_CSS_IN: return v * 25.4; + case GTK_CSS_CM: return v * 10; + default: return NAN; + } + } + else if (unit == GTK_CSS_EM) + { + switch ((int) value->dimension.unit) + { + case GTK_CSS_EX: return v * 0.5; + default: return NAN; + } + } + else if (unit == GTK_CSS_DEG) + { + switch ((int) value->dimension.unit) + { + case GTK_CSS_RAD: return v * 180.0 / G_PI; + case GTK_CSS_GRAD: return v * 360.0 / 400.0; + case GTK_CSS_TURN: return v * 360.0; + default: return NAN; + } + } + else if (unit == GTK_CSS_S) + { + switch ((int) value->dimension.unit) + { + case GTK_CSS_MS: return v / 1000.0; + default: return NAN; + } + } + else + { + return NAN; + } +} + static GtkCssValue * gtk_css_value_number_compute (GtkCssValue *number, guint property_id, @@ -115,34 +309,32 @@ gtk_css_value_number_compute (GtkCssValue *number, GtkStyleProvider *provider = context->provider; GtkCssStyle *style = context->style; GtkCssStyle *parent_style = context->parent_style; - double value; - if (G_UNLIKELY (number->type == TYPE_CALC)) + if (number->type != TYPE_DIMENSION) { const guint n_terms = number->calc.n_terms; GtkCssValue *result; gboolean changed = FALSE; - gsize i; GtkCssValue **new_values; new_values = g_alloca (sizeof (GtkCssValue *) * n_terms); - for (i = 0; i < n_terms; i++) + for (gsize i = 0; i < n_terms; i++) { - GtkCssValue *computed = gtk_css_value_compute (number->calc.terms[i], - property_id, - context); + GtkCssValue *computed; + + computed = gtk_css_value_compute (number->calc.terms[i], property_id, context); changed |= computed != number->calc.terms[i]; new_values[i] = computed; } if (changed) { - result = gtk_css_calc_value_new_from_array (new_values, n_terms); + result = gtk_css_math_value_new (number->type, number->calc.mode, new_values, n_terms); } else { - for (i = 0; i < n_terms; i++) + for (gsize i = 0; i < n_terms; i++) gtk_css_value_unref (new_values[i]); result = gtk_css_value_ref (number); @@ -150,66 +342,66 @@ gtk_css_value_number_compute (GtkCssValue *number, return result; } - - g_assert (number->type == TYPE_DIMENSION); - - value = number->dimension.value; - switch (number->dimension.unit) + else { - case GTK_CSS_PERCENT: - /* percentages for font sizes are computed, other percentages aren't */ - if (property_id == GTK_CSS_PROPERTY_FONT_SIZE) - return gtk_css_dimension_value_new (value / 100.0 * - get_base_font_size_px (property_id, provider, style, parent_style), - GTK_CSS_PX); - G_GNUC_FALLTHROUGH; - case GTK_CSS_NUMBER: - case GTK_CSS_PX: - case GTK_CSS_DEG: - case GTK_CSS_S: - return gtk_css_value_ref (number); - case GTK_CSS_PT: - return gtk_css_dimension_value_new (value * get_dpi (style) / 72.0, - GTK_CSS_PX); - case GTK_CSS_PC: - return gtk_css_dimension_value_new (value * get_dpi (style) / 72.0 * 12.0, - GTK_CSS_PX); - case GTK_CSS_IN: - return gtk_css_dimension_value_new (value * get_dpi (style), - GTK_CSS_PX); - case GTK_CSS_CM: - return gtk_css_dimension_value_new (value * get_dpi (style) * 0.39370078740157477, - GTK_CSS_PX); - case GTK_CSS_MM: - return gtk_css_dimension_value_new (value * get_dpi (style) * 0.039370078740157477, - GTK_CSS_PX); - case GTK_CSS_EM: - return gtk_css_dimension_value_new (value * - get_base_font_size_px (property_id, provider, style, parent_style), - GTK_CSS_PX); - case GTK_CSS_EX: - /* for now we pretend ex is half of em */ - return gtk_css_dimension_value_new (value * 0.5 * - get_base_font_size_px (property_id, provider, style, parent_style), - GTK_CSS_PX); - case GTK_CSS_REM: - return gtk_css_dimension_value_new (value * - gtk_css_font_size_get_default_px (provider, style), - GTK_CSS_PX); - case GTK_CSS_RAD: - return gtk_css_dimension_value_new (value * 360.0 / (2 * G_PI), - GTK_CSS_DEG); - case GTK_CSS_GRAD: - return gtk_css_dimension_value_new (value * 360.0 / 400.0, - GTK_CSS_DEG); - case GTK_CSS_TURN: - return gtk_css_dimension_value_new (value * 360.0, - GTK_CSS_DEG); - case GTK_CSS_MS: - return gtk_css_dimension_value_new (value / 1000.0, - GTK_CSS_S); - default: - g_assert_not_reached(); + double value = number->dimension.value; + switch (number->dimension.unit) + { + case GTK_CSS_PERCENT: + /* percentages for font sizes are computed, other percentages aren't */ + if (property_id == GTK_CSS_PROPERTY_FONT_SIZE) + return gtk_css_dimension_value_new (value / 100.0 * + get_base_font_size_px (property_id, provider, style, parent_style), + GTK_CSS_PX); + G_GNUC_FALLTHROUGH; + case GTK_CSS_NUMBER: + case GTK_CSS_PX: + case GTK_CSS_DEG: + case GTK_CSS_S: + return gtk_css_value_ref (number); + case GTK_CSS_PT: + return gtk_css_dimension_value_new (value * get_dpi (style) / 72.0, + GTK_CSS_PX); + case GTK_CSS_PC: + return gtk_css_dimension_value_new (value * get_dpi (style) / 72.0 * 12.0, + GTK_CSS_PX); + case GTK_CSS_IN: + return gtk_css_dimension_value_new (value * get_dpi (style), + GTK_CSS_PX); + case GTK_CSS_CM: + return gtk_css_dimension_value_new (value * get_dpi (style) * 0.39370078740157477, + GTK_CSS_PX); + case GTK_CSS_MM: + return gtk_css_dimension_value_new (value * get_dpi (style) * 0.039370078740157477, + GTK_CSS_PX); + case GTK_CSS_EM: + return gtk_css_dimension_value_new (value * + get_base_font_size_px (property_id, provider, style, parent_style), + GTK_CSS_PX); + case GTK_CSS_EX: + /* for now we pretend ex is half of em */ + return gtk_css_dimension_value_new (value * 0.5 * + get_base_font_size_px (property_id, provider, style, parent_style), + GTK_CSS_PX); + case GTK_CSS_REM: + return gtk_css_dimension_value_new (value * + gtk_css_font_size_get_default_px (provider, style), + GTK_CSS_PX); + case GTK_CSS_RAD: + return gtk_css_dimension_value_new (value * 360.0 / (2 * G_PI), + GTK_CSS_DEG); + case GTK_CSS_GRAD: + return gtk_css_dimension_value_new (value * 360.0 / 400.0, + GTK_CSS_DEG); + case GTK_CSS_TURN: + return gtk_css_dimension_value_new (value * 360.0, + GTK_CSS_DEG); + case GTK_CSS_MS: + return gtk_css_dimension_value_new (value / 1000.0, + GTK_CSS_S); + default: + g_assert_not_reached(); + } } } @@ -217,26 +409,24 @@ static gboolean gtk_css_value_number_equal (const GtkCssValue *val1, const GtkCssValue *val2) { - guint i; - if (val1->type != val2->type) return FALSE; - if (G_LIKELY (val1->type == TYPE_DIMENSION)) + if (val1->type == TYPE_DIMENSION) { return val1->dimension.unit == val2->dimension.unit && val1->dimension.value == val2->dimension.value; } - - g_assert (val1->type == TYPE_CALC); - - if (val1->calc.n_terms != val2->calc.n_terms) - return FALSE; - - for (i = 0; i < val1->calc.n_terms; i++) + else { - if (!gtk_css_value_equal (val1->calc.terms[i], val2->calc.terms[i])) + if (val1->calc.n_terms != val2->calc.n_terms) return FALSE; + + for (guint i = 0; i < val1->calc.n_terms; i++) + { + if (!gtk_css_value_equal (val1->calc.terms[i], val2->calc.terms[i])) + return FALSE; + } } return TRUE; @@ -246,55 +436,106 @@ static void gtk_css_value_number_print (const GtkCssValue *value, GString *string) { - guint i; + const char *round_modes[] = { "nearest", "up", "down", "to-zero" }; - if (G_LIKELY (value->type == TYPE_DIMENSION)) + switch (value->type) { - const char *names[] = { - /* [GTK_CSS_NUMBER] = */ "", - /* [GTK_CSS_PERCENT] = */ "%", - /* [GTK_CSS_PX] = */ "px", - /* [GTK_CSS_PT] = */ "pt", - /* [GTK_CSS_EM] = */ "em", - /* [GTK_CSS_EX] = */ "ex", - /* [GTK_CSS_REM] = */ "rem", - /* [GTK_CSS_PC] = */ "pc", - /* [GTK_CSS_IN] = */ "in", - /* [GTK_CSS_CM] = */ "cm", - /* [GTK_CSS_MM] = */ "mm", - /* [GTK_CSS_RAD] = */ "rad", - /* [GTK_CSS_DEG] = */ "deg", - /* [GTK_CSS_GRAD] = */ "grad", - /* [GTK_CSS_TURN] = */ "turn", - /* [GTK_CSS_S] = */ "s", - /* [GTK_CSS_MS] = */ "ms", - }; - char buf[G_ASCII_DTOSTR_BUF_SIZE]; + case TYPE_DIMENSION: + { + const char *names[] = { + /* [GTK_CSS_NUMBER] = */ "", + /* [GTK_CSS_PERCENT] = */ "%", + /* [GTK_CSS_PX] = */ "px", + /* [GTK_CSS_PT] = */ "pt", + /* [GTK_CSS_EM] = */ "em", + /* [GTK_CSS_EX] = */ "ex", + /* [GTK_CSS_REM] = */ "rem", + /* [GTK_CSS_PC] = */ "pc", + /* [GTK_CSS_IN] = */ "in", + /* [GTK_CSS_CM] = */ "cm", + /* [GTK_CSS_MM] = */ "mm", + /* [GTK_CSS_RAD] = */ "rad", + /* [GTK_CSS_DEG] = */ "deg", + /* [GTK_CSS_GRAD] = */ "grad", + /* [GTK_CSS_TURN] = */ "turn", + /* [GTK_CSS_S] = */ "s", + /* [GTK_CSS_MS] = */ "ms", + }; + char buf[G_ASCII_DTOSTR_BUF_SIZE]; - if (isinf (value->dimension.value)) - g_string_append (string, "infinite"); - else + if (isinf (value->dimension.value)) + { + if (value->dimension.value > 0) + g_string_append (string, "infinite"); + else + g_string_append (string, "-infinite"); + } + else if (isnan (value->dimension.value)) + g_string_append (string, "NaN"); + else + { + g_ascii_dtostr (buf, sizeof (buf), value->dimension.value); + g_string_append (string, buf); + if (value->dimension.value != 0.0) + g_string_append (string, names[value->dimension.unit]); + } + + return; + } + + case TYPE_CLAMP: + { + GtkCssValue *min = value->calc.terms[0]; + GtkCssValue *center = value->calc.terms[1]; + GtkCssValue *max = value->calc.terms[2]; + + g_string_append (string, function_name[value->type]); + g_string_append_c (string, '('); + if (min != NULL) + gtk_css_value_print (min, string); + else + g_string_append (string, "none"); + g_string_append (string, ", "); + gtk_css_value_print (center, string); + g_string_append (string, ", "); + if (max != NULL) + gtk_css_value_print (max, string); + else + g_string_append (string, "none"); + g_string_append_c (string, ')'); + } + break; + + case TYPE_ROUND: + g_string_append (string, function_name[value->type]); + g_string_append_c (string, '('); + g_string_append (string, round_modes[value->calc.mode]); + g_string_append (string, ", "); + gtk_css_value_print (value->calc.terms[0], string); + if (value->calc.n_terms > 1) { - g_ascii_dtostr (buf, sizeof (buf), value->dimension.value); - g_string_append (string, buf); - if (value->dimension.value != 0.0) - g_string_append (string, names[value->dimension.unit]); + g_string_append (string, ", "); + gtk_css_value_print (value->calc.terms[1], string); } + g_string_append_c (string, ')'); + break; - return; + default: + { + const char *sep = value->type == TYPE_CALC ? " + " : (value->type == TYPE_PRODUCT ? " * " : ", "); + + g_string_append (string, function_name[value->type]); + g_string_append_c (string, '('); + gtk_css_value_print (value->calc.terms[0], string); + for (guint i = 1; i < value->calc.n_terms; i++) + { + g_string_append (string, sep); + gtk_css_value_print (value->calc.terms[i], string); + } + g_string_append_c (string, ')'); + } + break; } - - g_assert (value->type == TYPE_CALC); - - g_string_append (string, "calc("); - gtk_css_value_print (value->calc.terms[0], string); - - for (i = 1; i < value->calc.n_terms; i++) - { - g_string_append (string, " + "); - gtk_css_value_print (value->calc.terms[i], string); - } - g_string_append (string, ")"); } static GtkCssValue * @@ -350,13 +591,12 @@ gtk_css_value_calc_get_size (gsize n_terms) } static GtkCssValue * -gtk_css_calc_value_new (guint n_terms) +gtk_css_calc_value_alloc (guint n_terms) { GtkCssValue *result; result = gtk_css_value_alloc (>K_CSS_VALUE_NUMBER, gtk_css_value_calc_get_size (n_terms)); - result->type = TYPE_CALC; result->calc.n_terms = n_terms; return result; @@ -414,14 +654,8 @@ gtk_css_dimension_value_new (double value, break; case GTK_CSS_PX: - if (value == 0 || - value == 1 || - value == 2 || - value == 3 || - value == 4 || - value == 5 || - value == 6 || - value == 7 || + if (value == 0 || value == 1 || value == 2 || value == 3 || + value == 4 || value == 5 || value == 6 || value == 7 || value == 8) return gtk_css_value_ref (&px_singletons[(int) value]); if (value == 16) @@ -518,8 +752,6 @@ gtk_css_number_value_get_calc_term_order (const GtkCssValue *value) return 1000 + order_per_unit[value->dimension.unit]; } - g_assert (value->type == TYPE_CALC); - /* This should never be needed because calc() can't contain calc(), * but eh... */ @@ -590,7 +822,7 @@ gtk_css_calc_value_new_sum (GtkCssValue *value1, gtk_css_calc_array_add (array, gtk_css_value_ref (value2)); } - result = gtk_css_calc_value_new_from_array ((GtkCssValue **)array->pdata, array->len); + result = gtk_css_math_value_new (TYPE_CALC, 0, (GtkCssValue **)array->pdata, array->len); g_ptr_array_free (array, TRUE); return result; @@ -599,66 +831,195 @@ gtk_css_calc_value_new_sum (GtkCssValue *value1, GtkCssDimension gtk_css_number_value_get_dimension (const GtkCssValue *value) { - GtkCssDimension dimension = GTK_CSS_DIMENSION_PERCENTAGE; - guint i; - - if (G_LIKELY (value->type == TYPE_DIMENSION)) - return gtk_css_unit_get_dimension (value->dimension.unit); - - g_assert (value->type == TYPE_CALC); - - for (i = 0; i < value->calc.n_terms && dimension == GTK_CSS_DIMENSION_PERCENTAGE; i++) + switch ((NumberValueType) value->type) { - dimension = gtk_css_number_value_get_dimension (value->calc.terms[i]); - if (dimension != GTK_CSS_DIMENSION_PERCENTAGE) - break; - } + case TYPE_DIMENSION: + return gtk_css_unit_get_dimension (value->dimension.unit); - return dimension; + case TYPE_CALC: + case TYPE_MIN: + case TYPE_MAX: + case TYPE_HYPOT: + case TYPE_ABS: + case TYPE_ROUND: + case TYPE_MOD: + case TYPE_REM: + case TYPE_CLAMP: + { + GtkCssDimension dimension = GTK_CSS_DIMENSION_PERCENTAGE; + + for (guint i = 0; i < value->calc.n_terms && dimension == GTK_CSS_DIMENSION_PERCENTAGE; i++) + { + dimension = gtk_css_number_value_get_dimension (value->calc.terms[i]); + if (dimension != GTK_CSS_DIMENSION_PERCENTAGE) + break; + } + return dimension; + } + + case TYPE_PRODUCT: + if (gtk_css_number_value_get_dimension (value->calc.terms[0]) != GTK_CSS_DIMENSION_NUMBER) + return gtk_css_number_value_get_dimension (value->calc.terms[0]); + else + return gtk_css_number_value_get_dimension (value->calc.terms[1]); + + case TYPE_SIGN: + case TYPE_SIN: + case TYPE_COS: + case TYPE_TAN: + case TYPE_EXP: + case TYPE_SQRT: + case TYPE_POW: + case TYPE_LOG: + return GTK_CSS_DIMENSION_NUMBER; + + case TYPE_ASIN: + case TYPE_ACOS: + case TYPE_ATAN: + case TYPE_ATAN2: + return GTK_CSS_DIMENSION_ANGLE; + + default: + g_assert_not_reached (); + } } gboolean gtk_css_number_value_has_percent (const GtkCssValue *value) { - guint i; - - if (G_LIKELY (value->type == TYPE_DIMENSION)) + if (value->type == TYPE_DIMENSION) { return gtk_css_unit_get_dimension (value->dimension.unit) == GTK_CSS_DIMENSION_PERCENTAGE; } - - for (i = 0; i < value->calc.n_terms; i++) + else { - if (gtk_css_number_value_has_percent (value->calc.terms[i])) - return TRUE; - } + for (guint i = 0; i < value->calc.n_terms; i++) + { + if (gtk_css_number_value_has_percent (value->calc.terms[i])) + return TRUE; + } - return FALSE; + return FALSE; + } } GtkCssValue * gtk_css_number_value_multiply (GtkCssValue *value, double factor) { - GtkCssValue *result; - guint i; - if (factor == 1) return gtk_css_value_ref (value); - if (G_LIKELY (value->type == TYPE_DIMENSION)) + switch (value->type) { + case TYPE_DIMENSION: return gtk_css_dimension_value_new (value->dimension.value * factor, value->dimension.unit); + + case TYPE_MIN: + case TYPE_MAX: + case TYPE_MOD: + case TYPE_REM: + { + GtkCssValue **values; + guint type = value->type; + + values = g_new (GtkCssValue *, value->calc.n_terms); + for (guint i = 0; i < value->calc.n_terms; i++) + values[i] = gtk_css_number_value_multiply (value->calc.terms[i], factor); + + if (factor < 0) + { + if (type == TYPE_MIN) + type = TYPE_MAX; + else if (type == TYPE_MAX) + type = TYPE_MIN; + } + + return gtk_css_math_value_new (type, 0, values, value->calc.n_terms); + } + + case TYPE_CALC: + { + GtkCssValue *result = gtk_css_calc_value_alloc (value->calc.n_terms); + + result->type = value->type; + result->calc.mode = value->calc.mode; + for (guint i = 0; i < value->calc.n_terms; i++) + result->calc.terms[i] = gtk_css_number_value_multiply (value->calc.terms[i], factor); + return result; + } + + case TYPE_PRODUCT: + { + GtkCssValue *result = gtk_css_calc_value_alloc (value->calc.n_terms); + gboolean found = FALSE; + + result->type = value->type; + result->calc.mode = value->calc.mode; + for (guint i = 0; i < value->calc.n_terms; i++) + { + if (!found && + value->calc.terms[i]->type == TYPE_DIMENSION && + value->calc.terms[i]->dimension.unit == GTK_CSS_NUMBER) + { + result->calc.terms[i] = gtk_css_number_value_multiply (value->calc.terms[i], factor); + found = TRUE; + } + else + { + result->calc.terms[i] = gtk_css_value_ref (value->calc.terms[i]); + } + } + + if (found) + return result; + + gtk_css_value_unref (result); + } + break; + + case TYPE_ROUND: + { + GtkCssValue *a = gtk_css_number_value_multiply (value->calc.terms[0], factor); + GtkCssValue *b = value->calc.n_terms > 0 + ? gtk_css_number_value_multiply (value->calc.terms[1], factor) + : _gtk_css_number_value_new (factor, GTK_CSS_NUMBER); + + return gtk_css_round_value_new (value->calc.mode, a, b); + } + + case TYPE_CLAMP: + { + GtkCssValue *min = value->calc.terms[0]; + GtkCssValue *center = value->calc.terms[1]; + GtkCssValue *max = value->calc.terms[2]; + + if (min) + min = gtk_css_number_value_multiply (min, factor); + center = gtk_css_number_value_multiply (center, factor); + if (max) + max = gtk_css_number_value_multiply (max, factor); + + if (factor < 0) + { + GtkCssValue *tmp = min; + min = max; + max = tmp; + } + + return gtk_css_clamp_value_new (min, center, max); + } + + default: + break; } - g_assert (value->type == TYPE_CALC); - - result = gtk_css_calc_value_new (value->calc.n_terms); - for (i = 0; i < value->calc.n_terms; i++) - result->calc.terms[i] = gtk_css_number_value_multiply (value->calc.terms[i], factor); - - return result; + return gtk_css_math_value_new (TYPE_PRODUCT, 0, + (GtkCssValue *[]) { + gtk_css_value_ref (value), + _gtk_css_number_value_new (factor, GTK_CSS_NUMBER) + }, 2); } GtkCssValue * @@ -683,7 +1044,10 @@ gtk_css_number_value_try_add (GtkCssValue *value1, if (G_LIKELY (value1->type == TYPE_DIMENSION)) { - if (value1->dimension.unit != value2->dimension.unit) + GtkCssUnit unit = canonical_unit (value1->dimension.unit); + double v1, v2; + + if (unit != canonical_unit (value2->dimension.unit)) return NULL; if (value1->dimension.value == 0) @@ -692,12 +1056,12 @@ gtk_css_number_value_try_add (GtkCssValue *value1, if (value2->dimension.value == 0) return gtk_css_value_ref (value1); - return gtk_css_dimension_value_new (value1->dimension.value + value2->dimension.value, - value1->dimension.unit); + v1 = get_converted_value (value1, unit); + v2 = get_converted_value (value2, unit); + + return gtk_css_dimension_value_new (v1 + v2, unit); } - g_assert (value1->type == TYPE_CALC); - /* Not possible for calc() values */ return NULL; } @@ -708,53 +1072,647 @@ _gtk_css_number_value_new (double value, return gtk_css_dimension_value_new (value, unit); } +static GtkCssValue * +gtk_css_clamp_value_new (GtkCssValue *min, + GtkCssValue *center, + GtkCssValue *max) +{ + GtkCssValue *values[] = { min, center, max }; + GtkCssUnit unit; + double min_, center_, max_, v; + + if (min == NULL && max == NULL) + return center; + + if (!units_compatible (center, min) || !units_compatible (center, max)) + return gtk_css_calc_value_new (TYPE_CLAMP, 0, values, 3); + + unit = canonical_unit (center->dimension.unit); + min_ = min ? get_converted_value (min, unit) : -INFINITY; + center_ = get_converted_value (center, unit); + max_ = max ? get_converted_value (max, unit) : INFINITY; + + v = CLAMP (center_, min_, max_); + + g_clear_pointer (&min, gtk_css_value_unref); + g_clear_pointer (¢er, gtk_css_value_unref); + g_clear_pointer (&max, gtk_css_value_unref); + + return gtk_css_dimension_value_new (v, unit); +} + +static GtkCssValue * +gtk_css_round_value_new (guint mode, + GtkCssValue *a, + GtkCssValue *b) +{ + GtkCssValue *values[2] = { a, b }; + GtkCssUnit unit; + double a_, b_, v; + + if (!units_compatible (a, b)) + return gtk_css_calc_value_new (TYPE_ROUND, mode, values, b != NULL ? 2 : 1); + + unit = canonical_unit (a->dimension.unit); + a_ = get_converted_value (a, unit); + b_ = b ? get_converted_value (b, unit) : 1; + + v = _round (mode, a_, b_); + + gtk_css_value_unref (a); + gtk_css_value_unref (b); + + return gtk_css_dimension_value_new (v, unit); +} + +static GtkCssValue * +gtk_css_minmax_value_new (guint type, + GtkCssValue **values, + guint n_values) +{ + GtkCssValue **vals; + guint n_vals; + + if (n_values == 1) + return values[0]; + + vals = g_newa (GtkCssValue *, n_values); + memset (vals, 0, sizeof (GtkCssValue *) * n_values); + + n_vals = 0; + + for (guint i = 0; i < n_values; i++) + { + GtkCssValue *value = values[i]; + + if (value->type == TYPE_DIMENSION) + { + GtkCssUnit unit; + double v; + + unit = canonical_unit (value->dimension.unit); + v = get_converted_value (value, unit); + + for (guint j = 0; j < n_vals; j++) + { + if ((vals[j]->type == TYPE_DIMENSION) && + (canonical_unit (vals[j]->dimension.unit) == unit)) + { + double v1 = get_converted_value (vals[j], unit); + + if ((type == TYPE_MIN && v < v1) || + (type == TYPE_MAX && v > v1)) + { + gtk_css_value_unref (vals[j]); + vals[j] = g_steal_pointer (&value); + } + else + { + g_clear_pointer (&value, gtk_css_value_unref); + } + + break; + } + } + } + + if (value) + { + vals[n_vals] = value; + n_vals++; + } + } + + return gtk_css_calc_value_new (type, 0, vals, n_vals); +} + +static GtkCssValue * +gtk_css_hypot_value_new (GtkCssValue **values, + guint n_values) +{ + GtkCssUnit unit; + double acc; + double v; + + for (guint i = 0; i < n_values; i++) + { + if (value_is_compute_time (values[i])) + return gtk_css_calc_value_new (TYPE_HYPOT, 0, values, n_values); + } + + unit = canonical_unit (values[0]->dimension.unit); + acc = 0; + + for (guint i = 0; i < n_values; i++) + { + double a = get_converted_value (values[i], unit); + acc += a * a; + } + + v = sqrt (acc); + + for (guint i = 0; i < n_values; i++) + gtk_css_value_unref (values[i]); + + return gtk_css_dimension_value_new (v, unit); +} + +static GtkCssValue * +gtk_css_arg1_value_new (guint type, + GtkCssValue *value) +{ + GtkCssUnit unit; + double a; + double v; + + if (value_is_compute_time (value)) + return gtk_css_calc_value_new (type, 0, &value, 1); + + a = get_converted_value (value, canonical_unit (value->dimension.unit)); + + if (type == TYPE_SIN || type == TYPE_COS || type == TYPE_TAN) + { + if (gtk_css_unit_get_dimension (value->dimension.unit) == GTK_CSS_DIMENSION_ANGLE) + a = DEG_TO_RAD (a); + } + + switch (type) + { + case TYPE_SIN: v = sin (a); break; + case TYPE_COS: v = cos (a); break; + case TYPE_TAN: v = tan (a); break; + case TYPE_ASIN: v = asin (a); break; + case TYPE_ACOS: v = acos (a); break; + case TYPE_ATAN: v = atan (a); break; + case TYPE_SQRT: v = sqrt (a); break; + case TYPE_EXP: v = exp (a); break; + case TYPE_ABS: v = fabs (a); break; + case TYPE_SIGN: v = _sign (a); break; + default: g_assert_not_reached (); + } + + if (type == TYPE_ASIN || type == TYPE_ACOS || type == TYPE_ATAN) + { + unit = GTK_CSS_DEG; + v = RAD_TO_DEG (v); + } + else if (type == TYPE_ABS) + { + unit = value->dimension.unit; + } + else + { + unit = GTK_CSS_NUMBER; + } + + gtk_css_value_unref (value); + + return gtk_css_dimension_value_new (v, unit); +} + +static GtkCssValue * +gtk_css_arg2_value_new (guint type, + GtkCssValue *value1, + GtkCssValue *value2) +{ + GtkCssValue *values[2] = { value1, value2 }; + GtkCssUnit unit; + double a, b = 1; + double v; + + if (value_is_compute_time (value1) || + (value2 && value_is_compute_time (value2))) + return gtk_css_calc_value_new (type, 0, values, 2); + + a = get_converted_value (value1, canonical_unit (value1->dimension.unit)); + if (value2) + b = get_converted_value (value2, canonical_unit (value2->dimension.unit)); + + switch (type) + { + case TYPE_MOD: v = _mod (a, b); break; + case TYPE_REM: v = _rem (a, b); break; + case TYPE_ATAN2: v = atan2 (a, b); break; + case TYPE_POW: v = pow (a, b); break; + case TYPE_LOG: v = value2 ? log (a) / log (b) : log (a); break; + default: g_assert_not_reached (); + } + + if (type == TYPE_ATAN2) + { + unit = GTK_CSS_DEG; + v = RAD_TO_DEG (v); + } + else + { + unit = GTK_CSS_NUMBER; + } + + gtk_css_value_unref (value1); + gtk_css_value_unref (value2); + + return gtk_css_dimension_value_new (v, unit); +} + +/* This funciton is called at parsing time, so units are not + * canonical, and length values can't necessarily be unified. + */ +GtkCssValue * +gtk_css_math_value_new (guint type, + guint mode, + GtkCssValue **values, + guint n_values) +{ + switch ((NumberValueType) type) + { + case TYPE_DIMENSION: + g_assert_not_reached (); + + case TYPE_ROUND: + return gtk_css_round_value_new (mode, values[0], values[1]); + + case TYPE_CLAMP: + return gtk_css_clamp_value_new (values[0], values[1], values[2]); + + case TYPE_HYPOT: + return gtk_css_hypot_value_new (values, n_values); + + case TYPE_MIN: + case TYPE_MAX: + return gtk_css_minmax_value_new (type, values, n_values); + + case TYPE_SIN: + case TYPE_COS: + case TYPE_TAN: + case TYPE_ASIN: + case TYPE_ACOS: + case TYPE_ATAN: + case TYPE_SQRT: + case TYPE_EXP: + case TYPE_ABS: + case TYPE_SIGN: + return gtk_css_arg1_value_new (type, values[0]); + + case TYPE_MOD: + case TYPE_REM: + case TYPE_ATAN2: + case TYPE_POW: + case TYPE_LOG: + return gtk_css_arg2_value_new (type, values[0], values[1]); + + case TYPE_PRODUCT: + case TYPE_CALC: + default: + return gtk_css_calc_value_new (type, mode, values, n_values); + } +} + gboolean gtk_css_number_value_can_parse (GtkCssParser *parser) { - return gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_NUMBER) - || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_NUMBER) - || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_INTEGER) - || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER) - || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_PERCENTAGE) - || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION) - || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION) - || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_DIMENSION) - || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_DIMENSION) - || gtk_css_parser_has_function (parser, "calc"); + const GtkCssToken *token = gtk_css_parser_get_token (parser); + + switch ((int) token->type) + { + case GTK_CSS_TOKEN_SIGNED_NUMBER: + case GTK_CSS_TOKEN_SIGNLESS_NUMBER: + case GTK_CSS_TOKEN_SIGNED_INTEGER: + case GTK_CSS_TOKEN_SIGNLESS_INTEGER: + case GTK_CSS_TOKEN_PERCENTAGE: + case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION: + case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION: + case GTK_CSS_TOKEN_SIGNED_DIMENSION: + case GTK_CSS_TOKEN_SIGNLESS_DIMENSION: + return TRUE; + + case GTK_CSS_TOKEN_FUNCTION: + { + const char *name = gtk_css_token_get_string (token); + + for (guint i = 0; i < G_N_ELEMENTS (function_name); i++) + { + if (g_ascii_strcasecmp (function_name[i], name) == 0) + return TRUE; + } + } + break; + + default: + break; + } + + return FALSE; } GtkCssValue * _gtk_css_number_value_parse (GtkCssParser *parser, GtkCssNumberParseFlags flags) { - if (gtk_css_parser_has_function (parser, "calc")) - return gtk_css_calc_value_parse (parser, flags); + const GtkCssToken *token = gtk_css_parser_get_token (parser); + + if (gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION)) + { + const char *name = gtk_css_token_get_string (token); + + if (g_ascii_strcasecmp (name, "calc") == 0) + return gtk_css_calc_value_parse (parser, flags); + else if (g_ascii_strcasecmp (name, "min") == 0) + return gtk_css_argn_value_parse (parser, flags, "min", TYPE_MIN); + else if (g_ascii_strcasecmp (name, "max") == 0) + return gtk_css_argn_value_parse (parser, flags, "max", TYPE_MAX); + else if (g_ascii_strcasecmp (name, "hypot") == 0) + return gtk_css_argn_value_parse (parser, flags, "hypot", TYPE_HYPOT); + else if (g_ascii_strcasecmp (name, "clamp") == 0) + return gtk_css_clamp_value_parse (parser, flags, TYPE_CLAMP); + else if (g_ascii_strcasecmp (name, "round") == 0) + return gtk_css_round_value_parse (parser, flags, TYPE_ROUND); + else if (g_ascii_strcasecmp (name, "mod") == 0) + return gtk_css_arg2_value_parse (parser, flags, 2, 2, "mod", TYPE_MOD); + else if (g_ascii_strcasecmp (name, "rem") == 0) + return gtk_css_arg2_value_parse (parser, flags, 2, 2, "rem", TYPE_REM); + else if (g_ascii_strcasecmp (name, "abs") == 0) + return gtk_css_arg2_value_parse (parser, flags, 1, 1, "abs", TYPE_ABS); + else if ((flags & GTK_CSS_PARSE_NUMBER) && g_ascii_strcasecmp (name, "sign") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER|GTK_CSS_PARSE_DIMENSION|GTK_CSS_PARSE_PERCENT, 1, 1, "sign", TYPE_SIGN); + else if ((flags & GTK_CSS_PARSE_NUMBER) && g_ascii_strcasecmp (name, "sin") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER|GTK_CSS_PARSE_ANGLE, 1, 1, "sin", TYPE_SIN); + else if ((flags & GTK_CSS_PARSE_NUMBER) && g_ascii_strcasecmp (name, "cos") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER|GTK_CSS_PARSE_ANGLE, 1, 1, "cos", TYPE_COS); + else if ((flags & GTK_CSS_PARSE_NUMBER) && g_ascii_strcasecmp (name, "tan") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER|GTK_CSS_PARSE_ANGLE, 1, 1, "tan", TYPE_TAN); + else if ((flags & GTK_CSS_PARSE_ANGLE) && g_ascii_strcasecmp (name, "asin") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER, 1, 1, "asin", TYPE_ASIN); + else if ((flags & GTK_CSS_PARSE_ANGLE) && g_ascii_strcasecmp (name, "acos") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER, 1, 1, "acos", TYPE_ACOS); + else if ((flags & GTK_CSS_PARSE_ANGLE) && g_ascii_strcasecmp (name, "atan") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER, 1, 1, "atan", TYPE_ATAN); + else if ((flags & GTK_CSS_PARSE_ANGLE) && g_ascii_strcasecmp (name, "atan2") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER|GTK_CSS_PARSE_DIMENSION|GTK_CSS_PARSE_PERCENT, 2, 2, "atan2", TYPE_ATAN2); + else if ((flags & GTK_CSS_PARSE_NUMBER) && g_ascii_strcasecmp (name, "pow") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER, 2, 2, "pow", TYPE_POW); + else if ((flags & GTK_CSS_PARSE_NUMBER) && g_ascii_strcasecmp (name, "sqrt") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER, 1, 1, "sqrt", TYPE_SQRT); + else if ((flags & GTK_CSS_PARSE_NUMBER) && g_ascii_strcasecmp (name, "exp") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER, 1, 1, "exp", TYPE_EXP); + else if ((flags & GTK_CSS_PARSE_NUMBER) && g_ascii_strcasecmp (name, "log") == 0) + return gtk_css_arg2_value_parse (parser, GTK_CSS_PARSE_NUMBER, 1, 2, "log", TYPE_LOG); + } + else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT)) + { + const char *name = gtk_css_token_get_string (token); + struct { + const char *name; + double value; + } constants[] = { + { "e", G_E }, + { "pi", G_PI }, + { "infinity", INFINITY }, + { "-infinity", -INFINITY }, + { "NaN", NAN }, + }; + + for (guint i = 0; i < G_N_ELEMENTS (constants); i++) + { + if (g_ascii_strcasecmp (name, constants[i].name) == 0) + { + gtk_css_parser_consume_token (parser); + return _gtk_css_number_value_new (constants[i].value, GTK_CSS_NUMBER); + } + } + } return gtk_css_dimension_value_parse (parser, flags); } +/* This function is safe to call on computed values, since all + * units are canonical and all lengths are in px at that time. + */ double _gtk_css_number_value_get (const GtkCssValue *value, double one_hundred_percent) { - double result; - guint i; + guint type = value->type; + guint mode = value->calc.mode; + GtkCssValue * const *terms = value->calc.terms; + guint n_terms = value->calc.n_terms; - if (G_LIKELY (value->type == TYPE_DIMENSION)) + switch ((NumberValueType) type) { + case TYPE_DIMENSION: if (value->dimension.unit == GTK_CSS_PERCENT) return value->dimension.value * one_hundred_percent / 100; else return value->dimension.value; + + case TYPE_CALC: + { + double result = 0.0; + + for (guint i = 0; i < n_terms; i++) + result += _gtk_css_number_value_get (terms[i], one_hundred_percent); + + return result; + } + + case TYPE_PRODUCT: + { + double result = 1.0; + + for (guint i = 0; i < n_terms; i++) + result *= _gtk_css_number_value_get (terms[i], one_hundred_percent); + + return result; + } + + case TYPE_MIN: + { + double result = G_MAXDOUBLE; + + for (guint i = 0; i < n_terms; i++) + result = MIN (result, _gtk_css_number_value_get (terms[i], one_hundred_percent)); + + return result; + } + + case TYPE_MAX: + { + double result = -G_MAXDOUBLE; + + for (guint i = 0; i < n_terms; i++) + result = MAX (result, _gtk_css_number_value_get (terms[i], one_hundred_percent)); + + return result; + } + + case TYPE_CLAMP: + { + GtkCssValue *min = terms[0]; + GtkCssValue *center = terms[1]; + GtkCssValue *max = terms[2]; + double result; + + result = _gtk_css_number_value_get (center, one_hundred_percent); + + if (max) + result = MIN (result, _gtk_css_number_value_get (max, one_hundred_percent)); + if (min) + result = MAX (result, _gtk_css_number_value_get (min, one_hundred_percent)); + + return result; + } + + case TYPE_ROUND: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + double b = terms[1] != NULL ? _gtk_css_number_value_get (terms[1], one_hundred_percent) : 1; + + return _round (mode, a, b); + } + + case TYPE_MOD: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + double b = _gtk_css_number_value_get (terms[1], one_hundred_percent); + + return _mod (a, b); + } + + case TYPE_REM: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + double b = _gtk_css_number_value_get (terms[1], one_hundred_percent); + + return _rem (a, b); + } + + case TYPE_ABS: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + return fabs (a); + } + + case TYPE_SIGN: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + return _sign (a); + } + + case TYPE_SIN: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + if (gtk_css_number_value_get_dimension (value) == GTK_CSS_DIMENSION_ANGLE) + a = DEG_TO_RAD (a); + + return sin (a); + } + + case TYPE_COS: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + if (gtk_css_number_value_get_dimension (value) == GTK_CSS_DIMENSION_ANGLE) + a = DEG_TO_RAD (a); + + return cos (a); + } + + case TYPE_TAN: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + if (gtk_css_number_value_get_dimension (value) == GTK_CSS_DIMENSION_ANGLE) + a = DEG_TO_RAD (a); + + return tan (a); + } + + case TYPE_ASIN: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + return RAD_TO_DEG (asin (a)); + } + + case TYPE_ACOS: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + return RAD_TO_DEG (acos (a)); + } + + case TYPE_ATAN: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + return RAD_TO_DEG (atan (a)); + } + + case TYPE_ATAN2: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + double b = _gtk_css_number_value_get (terms[1], one_hundred_percent); + + return RAD_TO_DEG (atan2 (a, b)); + } + + case TYPE_POW: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + double b = _gtk_css_number_value_get (terms[1], one_hundred_percent); + + return pow (a, b); + } + + case TYPE_SQRT: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + return sqrt (a); + } + + case TYPE_EXP: + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + return exp (a); + } + + case TYPE_LOG: + if (n_terms > 1) + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + double b = _gtk_css_number_value_get (terms[1], one_hundred_percent); + + return log (a) / log (b); + } + else + { + double a = _gtk_css_number_value_get (terms[0], one_hundred_percent); + + return log (a); + } + + case TYPE_HYPOT: + { + double acc = 0; + + for (guint i = 0; i < n_terms; i++) + { + double a = _gtk_css_number_value_get (terms[i], one_hundred_percent); + + acc += a * a; + } + + return sqrt (acc); + } + + default: + g_assert_not_reached (); } - - g_assert (value->type == TYPE_CALC); - - result = 0.0; - for (i = 0; i < value->calc.n_terms; i++) - result += _gtk_css_number_value_get (value->calc.terms[i], one_hundred_percent); - - return result; } gboolean @@ -768,3 +1726,88 @@ gtk_css_dimension_value_is_zero (const GtkCssValue *value) return value->dimension.value == 0; } + +static double +_round (guint mode, double a, double b) +{ + int old_mode; + int modes[] = { FE_TONEAREST, FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO }; + double result; + + if (b == 0) + return NAN; + + if (isinf (a)) + { + if (isinf (b)) + return NAN; + else + return a; + } + + if (isinf (b)) + { + switch (mode) + { + case ROUND_NEAREST: + case ROUND_TO_ZERO: + return 0; + case ROUND_UP: + return a > 0 ? INFINITY : 0; + case ROUND_DOWN: + return a < 0 ? -INFINITY : 0; + default: + g_assert_not_reached (); + } + } + + old_mode = fegetround (); + fesetround (modes[mode]); + + result = nearbyint (a/b) * b; + + fesetround (old_mode); + + return result; +} + +static double +_mod (double a, double b) +{ + double z; + + if (b == 0 || isinf (a)) + return NAN; + + if (isinf (b) && (a < 0) != (b < 0)) + return NAN; + + z = fmod (a, b); + if (z < 0) + z += b; + + return z; +} + +static double +_rem (double a, double b) +{ + if (b == 0 || isinf (a)) + return NAN; + + if (isinf (b)) + return a; + + return fmod (a, b); +} + +static double +_sign (double a) +{ + if (a < 0) + return -1; + else if (a > 0) + return 1; + else + return 0; +} diff --git a/gtk/gtkcssnumbervalueprivate.h b/gtk/gtkcssnumbervalueprivate.h index e205edfac4..a5372d839b 100644 --- a/gtk/gtkcssnumbervalueprivate.h +++ b/gtk/gtkcssnumbervalueprivate.h @@ -36,6 +36,8 @@ typedef enum /*< skip >*/ { GTK_CSS_PARSE_TIME = (1 << 5) } GtkCssNumberParseFlags; +#define GTK_CSS_PARSE_DIMENSION (GTK_CSS_PARSE_LENGTH|GTK_CSS_PARSE_ANGLE|GTK_CSS_PARSE_TIME) + GtkCssValue * gtk_css_dimension_value_new (double value, GtkCssUnit unit); @@ -58,5 +60,17 @@ double _gtk_css_number_value_get (const GtkCssValue *num gboolean gtk_css_dimension_value_is_zero (const GtkCssValue *value) G_GNUC_PURE; +enum { + ROUND_NEAREST, + ROUND_UP, + ROUND_DOWN, + ROUND_TO_ZERO, +}; + +GtkCssValue * gtk_css_math_value_new (guint type, + guint mode, + GtkCssValue **values, + guint n_values); + G_END_DECLS diff --git a/testsuite/css/parser/calc.ref.css b/testsuite/css/parser/calc.ref.css index eb36835552..86fe46368d 100644 --- a/testsuite/css/parser/calc.ref.css +++ b/testsuite/css/parser/calc.ref.css @@ -3,7 +3,7 @@ a { } b { - transition-duration: calc(-200ms + 1s); + transition-duration: 0.80000000000000004s; } c { diff --git a/testsuite/css/parser/math.css b/testsuite/css/parser/math.css new file mode 100644 index 0000000000..dae1bc9f11 --- /dev/null +++ b/testsuite/css/parser/math.css @@ -0,0 +1,88 @@ +a { + margin-left: min(3px, 1em, 20cm); + margin-right: min(3px, 5px, 30px); + margin-top: calc(1px * min(3, 5, 10)); +} + +b { + padding-left: max(3px, 4em, 2px, 20cm); + padding-top: calc(1cm * max(3, 5, 7)); + transition-duration: max(1s, 100ms); +} + +c { + background-size: clamp(none, calc(pi * 1pt), 10cm); + border-left-width: clamp(2px, calc(e * 1px), none); + margin-left: clamp(2px, 1em, 10cm); +} + +d { + background-size: round(nearest, 17.3px, 2.2px); + border-left-width: round(down, 17px, 3ex); + border-right-width: calc(1px * round(17.5)); + border-top-width: round(up, 17px, 3px); +} + +e { + background-size: rem(17.3px, 2.2px); +} + +f { + border-top-left-radius: mod(-17.3px, 2.2px); +} + +g { + border-bottom-right-radius: calc(1px * sin(20)); +} + +h { + margin-left: calc(1px * cos(20)); +} + +i { + margin-right: calc(1px * tan(20)); +} + +j { + filter: hue-rotate(asin(20)); +} + +k { + filter: hue-rotate(acos(20)); +} + +l { + filter: hue-rotate(atan(20)); +} + +m { + filter: hue-rotate(atan2(20px, 30px)); +} + +n { + margin-top: calc(1px * pow(2, 3.5)); +} + +o { + margin-bottom: calc(1px * sqrt(2)); +} + +p { + padding-left: calc(1px * hypot(2, 3, 4, 5)); +} + +q { + padding-right: calc(1px * log(8, 3)); +} + +r { + padding-top: calc(1px * exp(5.4)); +} + +s { + padding-bottom: calc(1px * abs(sin(e))); +} + +t { + border-width: calc(1px * pow(pi, sign(-infinity))); +} diff --git a/testsuite/css/parser/math.ref.css b/testsuite/css/parser/math.ref.css new file mode 100644 index 0000000000..91d9c94a13 --- /dev/null +++ b/testsuite/css/parser/math.ref.css @@ -0,0 +1,91 @@ +a { + margin-left: min(3px, 1em, 20cm); + margin-right: 3px; + margin-top: 3px; +} + +b { + padding-left: max(3px, 4em, 20cm); + padding-top: 7cm; + transition-duration: 1s; +} + +c { + background-size: 1.1082840819977162mm; + border-left-width: 2.7182818284590451px; + margin-left: clamp(2px, 1em, 10cm); +} + +d { + background-size: 17.600000000000001px; + border-left-width: round(down, 17px, 3ex); + border-right-width: 18px; + border-top-width: 18px; +} + +e { + background-size: 1.8999999999999995; +} + +f { + border-top-left-radius: 0.30000000000000071; +} + +g { + border-bottom-right-radius: 0.91294525072762767px; +} + +h { + margin-left: 0.40808206181339196px; +} + +i { + margin-right: 2.2371609442247422px; +} + +j { + filter: hue-rotate(NaN); +} + +k { + filter: hue-rotate(NaN); +} + +l { + filter: hue-rotate(87.137594773888253deg); +} + +m { + filter: hue-rotate(33.690067525979785deg); +} + +n { + margin-top: 11.313708498984761px; +} + +o { + margin-bottom: 1.4142135623730951px; +} + +p { + padding-left: 7.3484692283495345px; +} + +q { + padding-right: 1.8927892607143719px; +} + +r { + padding-top: 221.40641620418717px; +} + +s { + padding-bottom: 0.41078129050290885px; +} + +t { + border-bottom-width: 0.31830988618379069px; + border-left-width: 0.31830988618379069px; + border-right-width: 0.31830988618379069px; + border-top-width: 0.31830988618379069px; +} diff --git a/testsuite/css/parser/math2.css b/testsuite/css/parser/math2.css new file mode 100644 index 0000000000..1243ce96d4 --- /dev/null +++ b/testsuite/css/parser/math2.css @@ -0,0 +1,50 @@ +a { + margin-left: calc(1px * mod(5, 0)); + margin-right: calc(1px * mod(infinity, 2)); + margin-top: calc(1px * mod(-1, infinity)); + + padding-left: mod(18px, 5px); + padding-right: mod(18px, -5px); + padding-top: mod(-18px, 5px); +} + +b { + margin-left: calc(1px * rem(5, 0)); + margin-right: calc(1px * rem(infinity, 2)); + margin-top: calc(1px * rem(-1, infinity)); + + padding-left: rem(18px, 5px); + padding-right: rem(18px, -5px); + padding-top: rem(-18px, 5px); +} + +c { + margin-bottom: round(to-zero, 18px, 5px); + margin-left: round(up, 18px, 5px); + margin-right: round(down, 18px, 5px); + margin-top: round(nearest, 18px, 5px); +} + +d { + margin-left: abs(20px); + margin-right: round(1px * cos(abs(-90deg)), 1px); + margin-top: calc(1px * sign(-20deg)); +} + +e { + margin-bottom: calc(1px * round(18, 0)); + margin-left: calc(1px * round(infinity, infinity)); + margin-right: calc(1px * round(infinity, 5)); + margin-top: calc(1px * round(18, infinity)); + + padding-bottom: calc(1px * round(nearest, 18, infinity)); + padding-left: calc(1px * round(up, 18, infinity)); + padding-right: calc(1px * round(down, -18, infinity)); + padding-top: calc(1px * round(to-zero, 18, infinity)); +} + +f { + margin-bottom: mod(18px, 0px); + margin-left: mod(calc(infinity * 1px), 5px); + margin-right: mod(-18px, calc(infinity * 1px)); +} diff --git a/testsuite/css/parser/math2.ref.css b/testsuite/css/parser/math2.ref.css new file mode 100644 index 0000000000..8f83291313 --- /dev/null +++ b/testsuite/css/parser/math2.ref.css @@ -0,0 +1,47 @@ +a { + margin-left: NaN; + margin-right: NaN; + margin-top: NaN; + padding-left: 3; + padding-right: 3; + padding-top: 2; +} + +b { + margin-left: NaN; + margin-right: NaN; + margin-top: -1px; + padding-left: 3; + padding-right: 3; + padding-top: -3; +} + +c { + margin-bottom: 15px; + margin-left: 20px; + margin-right: 15px; + margin-top: 20px; +} + +d { + margin-left: 20px; + margin-right: 0; + margin-top: -1px; +} + +e { + margin-bottom: NaN; + margin-left: NaN; + margin-right: infinite; + margin-top: 0; + padding-bottom: 0; + padding-left: infinite; + padding-right: -infinite; + padding-top: 0; +} + +f { + margin-bottom: NaN; + margin-left: NaN; + margin-right: NaN; +} diff --git a/testsuite/css/parser/meson.build b/testsuite/css/parser/meson.build index ee1714b503..6c4f3ce59c 100644 --- a/testsuite/css/parser/meson.build +++ b/testsuite/css/parser/meson.build @@ -414,6 +414,10 @@ test_data = [ 'line-height-invalid3.ref.css', 'margin.css', 'margin.ref.css', + 'math.css', + 'math.ref.css', + 'math2.css', + 'math2.ref.css', 'min-height.css', 'min-height.ref.css', 'min-width.css',