/* GTK - The GIMP Toolkit * Copyright © 2016 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 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 . */ #include "config.h" #include "gtkcsscalcvalueprivate.h" #include struct _GtkCssValue { GTK_CSS_VALUE_BASE gsize n_terms; GtkCssValue * terms[1]; }; static gsize gtk_css_value_calc_get_size (gsize n_terms) { g_assert (n_terms > 0); return sizeof (GtkCssValue) + sizeof (GtkCssValue *) * (n_terms - 1); } static void gtk_css_value_calc_free (GtkCssValue *value) { gsize i; for (i = 0; i < value->n_terms; i++) { _gtk_css_value_unref (value->terms[i]); } g_slice_free1 (gtk_css_value_calc_get_size (value->n_terms), value); } static GtkCssValue *gtk_css_calc_value_new (gsize n_terms); static GtkCssValue * gtk_css_value_new_from_array (GPtrArray *array) { GtkCssValue *result; if (array->len > 1) { result = gtk_css_calc_value_new (array->len); memcpy (result->terms, array->pdata, array->len * sizeof (GtkCssValue *)); } else { result = g_ptr_array_index (array, 0); } g_ptr_array_free (array, TRUE); return result; } static void gtk_css_calc_array_add (GPtrArray *array, GtkCssValue *value) { gsize i; gint calc_term_order; calc_term_order = gtk_css_number_value_get_calc_term_order (value); for (i = 0; i < array->len; i++) { GtkCssValue *sum = gtk_css_number_value_try_add (g_ptr_array_index (array, i), value); if (sum) { g_ptr_array_index (array, i) = sum; _gtk_css_value_unref (value); return; } else if (gtk_css_number_value_get_calc_term_order (g_ptr_array_index (array, i)) > calc_term_order) { g_ptr_array_insert (array, i, value); return; } } g_ptr_array_add (array, value); } static GtkCssValue * gtk_css_value_calc_compute (GtkCssValue *value, guint property_id, GtkStyleProvider *provider, GtkCssStyle *style, GtkCssStyle *parent_style) { GtkCssValue *result; GPtrArray *array; gboolean changed = FALSE; gsize i; array = g_ptr_array_new (); for (i = 0; i < value->n_terms; i++) { GtkCssValue *computed = _gtk_css_value_compute (value->terms[i], property_id, provider, style, parent_style); changed |= computed != value->terms[i]; gtk_css_calc_array_add (array, computed); } if (changed) { result = gtk_css_value_new_from_array (array); } else { g_ptr_array_set_free_func (array, (GDestroyNotify) _gtk_css_value_unref); g_ptr_array_free (array, TRUE); result = _gtk_css_value_ref (value); } return result; } static gboolean gtk_css_value_calc_equal (const GtkCssValue *value1, const GtkCssValue *value2) { gsize i; if (value1->n_terms != value2->n_terms) return FALSE; for (i = 0; i < value1->n_terms; i++) { if (!_gtk_css_value_equal (value1->terms[i], value2->terms[i])) return FALSE; } return TRUE; } static void gtk_css_value_calc_print (const GtkCssValue *value, GString *string) { gsize i; g_string_append (string, "calc("); _gtk_css_value_print (value->terms[0], string); for (i = 1; i < value->n_terms; i++) { g_string_append (string, " + "); _gtk_css_value_print (value->terms[i], string); } g_string_append (string, ")"); } static double gtk_css_value_calc_get (const GtkCssValue *value, double one_hundred_percent) { double result = 0.0; gsize i; for (i = 0; i < value->n_terms; i++) { result += _gtk_css_number_value_get (value->terms[i], one_hundred_percent); } return result; } static GtkCssDimension gtk_css_value_calc_get_dimension (const GtkCssValue *value) { GtkCssDimension dimension = GTK_CSS_DIMENSION_PERCENTAGE; gsize i; for (i = 0; i < value->n_terms && dimension == GTK_CSS_DIMENSION_PERCENTAGE; i++) { dimension = gtk_css_number_value_get_dimension (value->terms[i]); } return dimension; } static gboolean gtk_css_value_calc_has_percent (const GtkCssValue *value) { gsize i; for (i = 0; i < value->n_terms; i++) { if (gtk_css_number_value_has_percent (value->terms[i])) return TRUE; } return FALSE; } static GtkCssValue * gtk_css_value_calc_multiply (GtkCssValue *value, double factor) { GtkCssValue *result; gsize i; result = gtk_css_calc_value_new (value->n_terms); for (i = 0; i < value->n_terms; i++) { result->terms[i] = gtk_css_number_value_multiply (value->terms[i], factor); } return result; } static GtkCssValue * gtk_css_value_calc_try_add (GtkCssValue *value1, GtkCssValue *value2) { return NULL; } static gint gtk_css_value_calc_get_calc_term_order (const GtkCssValue *value) { /* This should never be needed because calc() can't contain calc(), * but eh... */ return 0; } static const GtkCssNumberValueClass GTK_CSS_VALUE_CALC = { { "GtkCssCalcValue", gtk_css_value_calc_free, gtk_css_value_calc_compute, gtk_css_value_calc_equal, gtk_css_number_value_transition, NULL, NULL, gtk_css_value_calc_print }, gtk_css_value_calc_get, gtk_css_value_calc_get_dimension, gtk_css_value_calc_has_percent, gtk_css_value_calc_multiply, gtk_css_value_calc_try_add, gtk_css_value_calc_get_calc_term_order }; static GtkCssValue * gtk_css_calc_value_new (gsize n_terms) { GtkCssValue *result; result = _gtk_css_value_alloc (>K_CSS_VALUE_CALC.value_class, gtk_css_value_calc_get_size (n_terms)); result->n_terms = n_terms; return result; } GtkCssValue * gtk_css_calc_value_new_sum (GtkCssValue *value1, GtkCssValue *value2) { GPtrArray *array; gsize i; array = g_ptr_array_new (); if (value1->class == >K_CSS_VALUE_CALC.value_class) { for (i = 0; i < value1->n_terms; i++) { gtk_css_calc_array_add (array, _gtk_css_value_ref (value1->terms[i])); } } else { gtk_css_calc_array_add (array, _gtk_css_value_ref (value1)); } if (value2->class == >K_CSS_VALUE_CALC.value_class) { for (i = 0; i < value2->n_terms; i++) { gtk_css_calc_array_add (array, _gtk_css_value_ref (value2->terms[i])); } } else { gtk_css_calc_array_add (array, _gtk_css_value_ref (value2)); } return gtk_css_value_new_from_array (array); } GtkCssValue * gtk_css_calc_value_parse_sum (GtkCssParser *parser, GtkCssNumberParseFlags flags); static GtkCssValue * gtk_css_calc_value_parse_value (GtkCssParser *parser, GtkCssNumberParseFlags flags) { if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_OPEN_PARENS)) { GtkCssValue *result; gtk_css_parser_start_block (parser); result = gtk_css_calc_value_parse_sum (parser, flags); if (result == NULL) { gtk_css_parser_end_block (parser); return NULL; } if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF)) { GtkCssLocation start = *gtk_css_parser_get_start_location (parser); gtk_css_parser_skip_until (parser, GTK_CSS_TOKEN_EOF); gtk_css_parser_error (parser, GTK_CSS_PARSER_ERROR_SYNTAX, &start, gtk_css_parser_get_start_location (parser), "Expected closing ')' in calc() subterm"); gtk_css_value_unref (result); gtk_css_parser_end_block (parser); return NULL; } gtk_css_parser_end_block (parser); return result; } return _gtk_css_number_value_parse (parser, flags); } static gboolean is_number (GtkCssValue *value) { return gtk_css_number_value_get_dimension (value) == GTK_CSS_DIMENSION_NUMBER && !gtk_css_number_value_has_percent (value); } static GtkCssValue * gtk_css_calc_value_parse_product (GtkCssParser *parser, GtkCssNumberParseFlags flags) { GtkCssValue *result, *value, *temp; GtkCssNumberParseFlags actual_flags; GtkCssLocation start; actual_flags = flags | GTK_CSS_PARSE_NUMBER; gtk_css_parser_get_token (parser); start = *gtk_css_parser_get_start_location (parser); result = gtk_css_calc_value_parse_value (parser, actual_flags); if (result == NULL) return NULL; while (TRUE) { if (actual_flags != GTK_CSS_PARSE_NUMBER && !is_number (result)) actual_flags = GTK_CSS_PARSE_NUMBER; if (gtk_css_parser_try_delim (parser, '*')) { value = gtk_css_calc_value_parse_product (parser, actual_flags); if (value == NULL) goto fail; if (is_number (value)) temp = gtk_css_number_value_multiply (result, _gtk_css_number_value_get (value, 100)); else temp = gtk_css_number_value_multiply (value, _gtk_css_number_value_get (result, 100)); _gtk_css_value_unref (value); _gtk_css_value_unref (result); result = temp; } else if (gtk_css_parser_try_delim (parser, '/')) { value = gtk_css_calc_value_parse_product (parser, GTK_CSS_PARSE_NUMBER); if (value == NULL) goto fail; temp = gtk_css_number_value_multiply (result, 1.0 / _gtk_css_number_value_get (value, 100)); _gtk_css_value_unref (value); _gtk_css_value_unref (result); result = temp; } else { break; } } if (is_number (result) && !(flags & GTK_CSS_PARSE_NUMBER)) { gtk_css_parser_error (parser, GTK_CSS_PARSER_ERROR_SYNTAX, &start, gtk_css_parser_get_start_location (parser), "calc() product term has no units"); goto fail; } return result; fail: _gtk_css_value_unref (result); return NULL; } GtkCssValue * gtk_css_calc_value_parse_sum (GtkCssParser *parser, GtkCssNumberParseFlags flags) { GtkCssValue *result; result = gtk_css_calc_value_parse_product (parser, flags); if (result == NULL) return NULL; while (TRUE) { GtkCssValue *next, *temp; if (gtk_css_parser_try_delim (parser, '+')) { next = gtk_css_calc_value_parse_product (parser, flags); if (next == NULL) goto fail; } else if (gtk_css_parser_try_delim (parser, '-')) { temp = gtk_css_calc_value_parse_product (parser, flags); if (temp == NULL) goto fail; next = gtk_css_number_value_multiply (temp, -1); _gtk_css_value_unref (temp); } else { break; } temp = gtk_css_number_value_add (result, next); _gtk_css_value_unref (result); _gtk_css_value_unref (next); result = temp; } return result; fail: _gtk_css_value_unref (result); return NULL; } typedef struct { GtkCssNumberParseFlags flags; GtkCssValue *value; } ParseCalcData; static guint gtk_css_calc_value_parse_arg (GtkCssParser *parser, guint arg, gpointer data_) { ParseCalcData *data = data_; data->value = gtk_css_calc_value_parse_sum (parser, data->flags); if (data->value == NULL) return 0; return 1; } GtkCssValue * gtk_css_calc_value_parse (GtkCssParser *parser, GtkCssNumberParseFlags flags) { ParseCalcData data; /* This can only be handled at compute time, we allow '-' after all */ data.flags = flags & ~GTK_CSS_POSITIVE_ONLY; data.value = NULL; if (!gtk_css_parser_has_function (parser, "calc")) { gtk_css_parser_error_syntax (parser, "Expected 'calc('"); return NULL; } if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_calc_value_parse_arg, &data)) return NULL; return data.value; }