gnome-shell-3.6.3.1/src/st/st-theme-node.c

No issues found

   1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
   2 /*
   3  * st-theme-node.c: style information for one node in a tree of themed objects
   4  *
   5  * Copyright 2008-2010 Red Hat, Inc.
   6  * Copyright 2009 Steve FrĂŠcinaux
   7  * Copyright 2009, 2010 Florian MĂźllner
   8  * Copyright 2010 Adel Gadllah
   9  * Copyright 2010 Giovanni Campagna
  10  * Copyright 2011 Quentin "Sardem FF7" Glidic
  11  *
  12  * This program is free software; you can redistribute it and/or modify
  13  * it under the terms of the GNU Lesser General Public License as
  14  * published by the Free Software Foundation, either version 2.1 of
  15  * the License, or (at your option) any later version.
  16  *
  17  * This program is distributed in the hope it will be useful, but WITHOUT ANY
  18  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  19  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
  20  * more details.
  21  *
  22  * You should have received a copy of the GNU Lesser General Public License
  23  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  24  */
  25 
  26 #include <stdlib.h>
  27 #include <string.h>
  28 
  29 #include "st-theme-private.h"
  30 #include "st-theme-context.h"
  31 #include "st-theme-node-private.h"
  32 
  33 static void st_theme_node_init               (StThemeNode          *node);
  34 static void st_theme_node_class_init         (StThemeNodeClass     *klass);
  35 static void st_theme_node_dispose           (GObject                 *object);
  36 static void st_theme_node_finalize           (GObject                 *object);
  37 
  38 static const ClutterColor BLACK_COLOR = { 0, 0, 0, 0xff };
  39 static const ClutterColor TRANSPARENT_COLOR = { 0, 0, 0, 0 };
  40 static const ClutterColor DEFAULT_SUCCESS_COLOR = { 0x4e, 0x9a, 0x06, 0xff };
  41 static const ClutterColor DEFAULT_WARNING_COLOR = { 0xf5, 0x79, 0x3e, 0xff };
  42 static const ClutterColor DEFAULT_ERROR_COLOR = { 0xcc, 0x00, 0x00, 0xff };
  43 
  44 extern gfloat st_slow_down_factor;
  45 
  46 G_DEFINE_TYPE (StThemeNode, st_theme_node, G_TYPE_OBJECT)
  47 
  48 static void
  49 st_theme_node_init (StThemeNode *node)
  50 {
  51   node->transition_duration = -1;
  52   _st_theme_node_init_drawing_state (node);
  53 }
  54 
  55 static void
  56 st_theme_node_class_init (StThemeNodeClass *klass)
  57 {
  58   GObjectClass *object_class = G_OBJECT_CLASS (klass);
  59 
  60   object_class->dispose = st_theme_node_dispose;
  61   object_class->finalize = st_theme_node_finalize;
  62 }
  63 
  64 
  65 static void
  66 st_theme_node_dispose (GObject *gobject)
  67 {
  68   StThemeNode *node = ST_THEME_NODE (gobject);
  69 
  70   if (node->parent_node)
  71     {
  72       g_object_unref (node->parent_node);
  73       node->parent_node = NULL;
  74     }
  75 
  76   if (node->border_image)
  77     {
  78       g_object_unref (node->border_image);
  79       node->border_image = NULL;
  80     }
  81 
  82   if (node->icon_colors)
  83     {
  84       st_icon_colors_unref (node->icon_colors);
  85       node->icon_colors = NULL;
  86     }
  87 
  88   g_clear_object (&node->theme);
  89 
  90   G_OBJECT_CLASS (st_theme_node_parent_class)->dispose (gobject);
  91 }
  92 
  93 static void
  94 st_theme_node_finalize (GObject *object)
  95 {
  96   StThemeNode *node = ST_THEME_NODE (object);
  97 
  98   g_free (node->element_id);
  99   g_free (node->element_class);
 100   g_free (node->pseudo_class);
 101   g_free (node->inline_style);
 102 
 103   if (node->properties)
 104     {
 105       g_free (node->properties);
 106       node->properties = NULL;
 107       node->n_properties = 0;
 108     }
 109 
 110   if (node->inline_properties)
 111     {
 112       /* This destroys the list, not just the head of the list */
 113       cr_declaration_destroy (node->inline_properties);
 114     }
 115 
 116   if (node->font_desc)
 117     {
 118       pango_font_description_free (node->font_desc);
 119       node->font_desc = NULL;
 120     }
 121 
 122   if (node->box_shadow)
 123     {
 124       st_shadow_unref (node->box_shadow);
 125       node->box_shadow = NULL;
 126     }
 127 
 128   if (node->background_image_shadow)
 129     {
 130       st_shadow_unref (node->background_image_shadow);
 131       node->background_image_shadow = NULL;
 132     }
 133 
 134   if (node->text_shadow)
 135     {
 136       st_shadow_unref (node->text_shadow);
 137       node->text_shadow = NULL;
 138     }
 139 
 140   if (node->background_image)
 141     g_free (node->background_image);
 142 
 143   _st_theme_node_free_drawing_state (node);
 144 
 145   G_OBJECT_CLASS (st_theme_node_parent_class)->finalize (object);
 146 }
 147 
 148 /**
 149  * st_theme_node_new:
 150  * @context: the context representing global state for this themed tree
 151  * @parent_node: (allow-none): the parent node of this node
 152  * @theme: (allow-none): a theme (stylesheet set) that overrides the
 153  *   theme inherited from the parent node
 154  * @element_type: the type of the GObject represented by this node
 155  *  in the tree (corresponding to an element if we were theming an XML
 156  *  document. %G_TYPE_NONE means this style was created for the stage
 157  * actor and matches a selector element name of 'stage'.
 158  * @element_id: (allow-none): the ID to match CSS rules against
 159  * @element_class: (allow-none): a whitespace-separated list of classes
 160  *   to match CSS rules against
 161  * @pseudo_class: (allow-none): a whitespace-separated list of pseudo-classes
 162  *   (like 'hover' or 'visited') to match CSS rules against
 163  *
 164  * Creates a new #StThemeNode. Once created, a node is immutable. Of any
 165  * of the attributes of the node (like the @element_class) change the node
 166  * and its child nodes must be destroyed and recreated.
 167  *
 168  * Return value: (transfer full): the theme node
 169  */
 170 StThemeNode *
 171 st_theme_node_new (StThemeContext    *context,
 172                    StThemeNode       *parent_node,
 173                    StTheme           *theme,
 174                    GType              element_type,
 175                    const char        *element_id,
 176                    const char        *element_class,
 177                    const char        *pseudo_class,
 178                    const char        *inline_style)
 179 {
 180   StThemeNode *node;
 181 
 182   g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
 183   g_return_val_if_fail (parent_node == NULL || ST_IS_THEME_NODE (parent_node), NULL);
 184 
 185   node = g_object_new (ST_TYPE_THEME_NODE, NULL);
 186 
 187   node->context = context;
 188   if (parent_node != NULL)
 189     node->parent_node = g_object_ref (parent_node);
 190   else
 191     node->parent_node = NULL;
 192 
 193   if (theme == NULL && parent_node != NULL)
 194     theme = parent_node->theme;
 195 
 196   if (theme != NULL)
 197     node->theme = theme;
 198 
 199   if (node->theme != NULL)
 200     g_object_ref (node->theme);
 201 
 202   node->element_type = element_type;
 203   node->element_id = g_strdup (element_id);
 204   node->element_class = g_strdup (element_class);
 205   node->pseudo_class = g_strdup (pseudo_class);
 206   node->inline_style = g_strdup (inline_style);
 207 
 208   return node;
 209 }
 210 
 211 /**
 212  * st_theme_node_get_parent:
 213  * @node: a #StThemeNode
 214  *
 215  * Gets the parent themed element node.
 216  *
 217  * Return value: (transfer none): the parent #StThemeNode, or %NULL if this
 218  *  is the root node of the tree of theme elements.
 219  */
 220 StThemeNode *
 221 st_theme_node_get_parent (StThemeNode *node)
 222 {
 223   g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
 224 
 225   return node->parent_node;
 226 }
 227 
 228 /**
 229  * st_theme_node_get_theme:
 230  * @node: a #StThemeNode
 231  *
 232  * Gets the theme stylesheet set that styles this node
 233  *
 234  * Return value: (transfer none): the theme stylesheet set
 235  */
 236 StTheme *
 237 st_theme_node_get_theme (StThemeNode *node)
 238 {
 239   g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
 240 
 241   return node->theme;
 242 }
 243 
 244 GType
 245 st_theme_node_get_element_type (StThemeNode *node)
 246 {
 247   g_return_val_if_fail (ST_IS_THEME_NODE (node), G_TYPE_NONE);
 248 
 249   return node->element_type;
 250 }
 251 
 252 const char *
 253 st_theme_node_get_element_id (StThemeNode *node)
 254 {
 255   g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
 256 
 257   return node->element_id;
 258 }
 259 
 260 const char *
 261 st_theme_node_get_element_class (StThemeNode *node)
 262 {
 263   g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
 264 
 265   return node->element_class;
 266 }
 267 
 268 const char *
 269 st_theme_node_get_pseudo_class (StThemeNode *node)
 270 {
 271   g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
 272 
 273   return node->pseudo_class;
 274 }
 275 
 276 /**
 277  * st_theme_node_equal:
 278  * @node_a: first #StThemeNode
 279  * @node_b: second #StThemeNode
 280  *
 281  * Compare two #StThemeNodes. Two nodes which compare equal will match
 282  * the same CSS rules and have the same style properties. However, two
 283  * nodes that have ended up with identical style properties do not
 284  * necessarily compare equal.
 285  * In detail, @node_a and @node_b are considered equal iff
 286  * <itemizedlist>
 287  *   <listitem>
 288  *     <para>they share the same #StTheme and #StThemeContext</para>
 289  *   </listitem>
 290  *   <listitem>
 291  *     <para>they have the same parent</para>
 292  *   </listitem>
 293  *   <listitem>
 294  *     <para>they have the same element type</para>
 295  *   </listitem>
 296  *   <listitem>
 297  *     <para>their id, class, pseudo-class and inline-style match</para>
 298  *   </listitem>
 299  * </itemizedlist>
 300  *
 301  * Returns: %TRUE if @node_a equals @node_b
 302  */
 303 gboolean
 304 st_theme_node_equal (StThemeNode *node_a, StThemeNode *node_b)
 305 {
 306   g_return_val_if_fail (ST_IS_THEME_NODE (node_a), FALSE);
 307   g_return_val_if_fail (ST_IS_THEME_NODE (node_b), FALSE);
 308 
 309   return node_a->parent_node == node_b->parent_node &&
 310          node_a->context == node_b->context &&
 311          node_a->theme == node_b->theme &&
 312          node_a->element_type == node_b->element_type &&
 313          !g_strcmp0 (node_a->element_id, node_b->element_id) &&
 314          !g_strcmp0 (node_a->element_class, node_b->element_class) &&
 315          !g_strcmp0 (node_a->pseudo_class, node_b->pseudo_class) &&
 316          !g_strcmp0 (node_a->inline_style, node_b->inline_style);
 317 }
 318 
 319 static void
 320 ensure_properties (StThemeNode *node)
 321 {
 322   if (!node->properties_computed)
 323     {
 324       GPtrArray *properties = NULL;
 325 
 326       node->properties_computed = TRUE;
 327 
 328       if (node->theme)
 329         properties = _st_theme_get_matched_properties (node->theme, node);
 330 
 331       if (node->inline_style)
 332         {
 333           CRDeclaration *cur_decl;
 334 
 335           if (!properties)
 336             properties = g_ptr_array_new ();
 337 
 338           node->inline_properties = _st_theme_parse_declaration_list (node->inline_style);
 339           for (cur_decl = node->inline_properties; cur_decl; cur_decl = cur_decl->next)
 340             g_ptr_array_add (properties, cur_decl);
 341         }
 342 
 343       if (properties)
 344         {
 345           node->n_properties = properties->len;
 346           node->properties = (CRDeclaration **)g_ptr_array_free (properties, FALSE);
 347         }
 348     }
 349 }
 350 
 351 typedef enum {
 352   VALUE_FOUND,
 353   VALUE_NOT_FOUND,
 354   VALUE_INHERIT
 355 } GetFromTermResult;
 356 
 357 static gboolean
 358 term_is_inherit (CRTerm *term)
 359 {
 360   return (term->type == TERM_IDENT &&
 361           strcmp (term->content.str->stryng->str, "inherit") == 0);
 362 }
 363 
 364 static gboolean
 365 term_is_none (CRTerm *term)
 366 {
 367   return (term->type == TERM_IDENT &&
 368           strcmp (term->content.str->stryng->str, "none") == 0);
 369 }
 370 
 371 static gboolean
 372 term_is_transparent (CRTerm *term)
 373 {
 374   return (term->type == TERM_IDENT &&
 375           strcmp (term->content.str->stryng->str, "transparent") == 0);
 376 }
 377 
 378 static int
 379 color_component_from_double (double component)
 380 {
 381   /* We want to spread the range 0-1 equally over 0..255, but
 382    * 1.0 should map to 255 not 256, so we need to special-case it.
 383    * See http://people.redhat.com/otaylor/pixel-converting.html
 384    * for (very) detailed discussion of related issues. */
 385   if (component >= 1.0)
 386     return 255;
 387   else
 388     return (int)(component * 256);
 389 }
 390 
 391 static GetFromTermResult
 392 get_color_from_rgba_term (CRTerm       *term,
 393                           ClutterColor *color)
 394 {
 395   CRTerm *arg = term->ext_content.func_param;
 396   CRNum *num;
 397   double r = 0, g = 0, b = 0, a = 0;
 398   int i;
 399 
 400   for (i = 0; i < 4; i++)
 401     {
 402       double value;
 403 
 404       if (arg == NULL)
 405         return VALUE_NOT_FOUND;
 406 
 407       if ((i == 0 && arg->the_operator != NO_OP) ||
 408           (i > 0 && arg->the_operator != COMMA))
 409         return VALUE_NOT_FOUND;
 410 
 411       if (arg->type != TERM_NUMBER)
 412         return VALUE_NOT_FOUND;
 413 
 414       num = arg->content.num;
 415 
 416       /* For simplicity, we convert a,r,g,b to [0,1.0] floats and then
 417        * convert them back below. Then when we set them on a cairo content
 418        * we convert them back to floats, and then cairo converts them
 419        * back to integers to pass them to X, and so forth...
 420        */
 421       if (i < 3)
 422         {
 423           if (num->type == NUM_PERCENTAGE)
 424             value = num->val / 100;
 425           else if (num->type == NUM_GENERIC)
 426             value = num->val / 255;
 427           else
 428             return VALUE_NOT_FOUND;
 429         }
 430       else
 431         {
 432           if (num->type != NUM_GENERIC)
 433             return VALUE_NOT_FOUND;
 434 
 435           value = num->val;
 436         }
 437 
 438       value = CLAMP (value, 0, 1);
 439 
 440       switch (i)
 441         {
 442         case 0:
 443           r = value;
 444           break;
 445         case 1:
 446           g = value;
 447           break;
 448         case 2:
 449           b = value;
 450           break;
 451         case 3:
 452           a = value;
 453           break;
 454         }
 455 
 456       arg = arg->next;
 457     }
 458 
 459   color->red = color_component_from_double (r);
 460   color->green = color_component_from_double (g);
 461   color->blue = color_component_from_double (b);
 462   color->alpha = color_component_from_double (a);
 463 
 464   return VALUE_FOUND;
 465 }
 466 
 467 static GetFromTermResult
 468 get_color_from_term (StThemeNode  *node,
 469                      CRTerm       *term,
 470                      ClutterColor *color)
 471 {
 472   CRRgb rgb;
 473   enum CRStatus status;
 474 
 475   /* Since libcroco doesn't know about rgba colors, it can't handle
 476    * the transparent keyword
 477    */
 478   if (term_is_transparent (term))
 479     {
 480       *color = TRANSPARENT_COLOR;
 481       return VALUE_FOUND;
 482     }
 483   /* rgba () colors - a CSS3 addition, are not supported by libcroco,
 484    * but they are parsed as a "function", so we can emulate the
 485    * functionality.
 486    */
 487   else if (term->type == TERM_FUNCTION &&
 488            term->content.str &&
 489            term->content.str->stryng &&
 490            term->content.str->stryng->str &&
 491            strcmp (term->content.str->stryng->str, "rgba") == 0)
 492     {
 493       return get_color_from_rgba_term (term, color);
 494     }
 495 
 496   status = cr_rgb_set_from_term (&rgb, term);
 497   if (status != CR_OK)
 498     return VALUE_NOT_FOUND;
 499 
 500   if (rgb.inherit)
 501     return VALUE_INHERIT;
 502 
 503   if (rgb.is_percentage)
 504     cr_rgb_compute_from_percentage (&rgb);
 505 
 506   color->red = rgb.red;
 507   color->green = rgb.green;
 508   color->blue = rgb.blue;
 509   color->alpha = 0xff;
 510 
 511   return VALUE_FOUND;
 512 }
 513 
 514 /**
 515  * st_theme_node_lookup_color:
 516  * @node: a #StThemeNode
 517  * @property_name: The name of the color property
 518  * @inherit: if %TRUE, if a value is not found for the property on the
 519  *   node, then it will be looked up on the parent node, and then on the
 520  *   parent's parent, and so forth. Note that if the property has a
 521  *   value of 'inherit' it will be inherited even if %FALSE is passed
 522  *   in for @inherit; this only affects the default behavior for inheritance.
 523  * @color: (out caller-allocates): location to store the color that was
 524  *   determined. If the property is not found, the value in this location
 525  *   will not be changed.
 526  *
 527  * Generically looks up a property containing a single color value. When
 528  * specific getters (like st_theme_node_get_background_color()) exist, they
 529  * should be used instead. They are cached, so more efficient, and have
 530  * handling for shortcut properties and other details of CSS.
 531  *
 532  * See also st_theme_node_get_color(), which provides a simpler API.
 533  *
 534  * Return value: %TRUE if the property was found in the properties for this
 535  *  theme node (or in the properties of parent nodes when inheriting.)
 536  */
 537 gboolean
 538 st_theme_node_lookup_color (StThemeNode  *node,
 539                             const char   *property_name,
 540                             gboolean      inherit,
 541                             ClutterColor *color)
 542 {
 543 
 544   int i;
 545 
 546   ensure_properties (node);
 547 
 548   for (i = node->n_properties - 1; i >= 0; i--)
 549     {
 550       CRDeclaration *decl = node->properties[i];
 551 
 552       if (strcmp (decl->property->stryng->str, property_name) == 0)
 553         {
 554           GetFromTermResult result = get_color_from_term (node, decl->value, color);
 555           if (result == VALUE_FOUND)
 556             {
 557               return TRUE;
 558             }
 559           else if (result == VALUE_INHERIT)
 560             {
 561               if (node->parent_node)
 562                 return st_theme_node_lookup_color (node->parent_node, property_name, inherit, color);
 563               else
 564                 break;
 565             }
 566         }
 567     }
 568 
 569   if (inherit && node->parent_node)
 570     return st_theme_node_lookup_color (node->parent_node, property_name, inherit, color);
 571 
 572   return FALSE;
 573 }
 574 
 575 /**
 576  * st_theme_node_get_color:
 577  * @node: a #StThemeNode
 578  * @property_name: The name of the color property
 579  * @color: (out caller-allocates): location to store the color that
 580  *   was determined.
 581  *
 582  * Generically looks up a property containing a single color value. When
 583  * specific getters (like st_theme_node_get_background_color()) exist, they
 584  * should be used instead. They are cached, so more efficient, and have
 585  * handling for shortcut properties and other details of CSS.
 586  *
 587  * If @property_name is not found, a warning will be logged and a
 588  * default color returned.
 589  *
 590  * See also st_theme_node_lookup_color(), which provides more options,
 591  * and lets you handle the case where the theme does not specify the
 592  * indicated color.
 593  */
 594 void
 595 st_theme_node_get_color (StThemeNode  *node,
 596                          const char   *property_name,
 597                          ClutterColor *color)
 598 {
 599   if (!st_theme_node_lookup_color (node, property_name, FALSE, color))
 600     {
 601       g_warning ("Did not find color property '%s'", property_name);
 602       memset (color, 0, sizeof (ClutterColor));
 603     }
 604 }
 605 
 606 /**
 607  * st_theme_node_lookup_double:
 608  * @node: a #StThemeNode
 609  * @property_name: The name of the numeric property
 610  * @inherit: if %TRUE, if a value is not found for the property on the
 611  *   node, then it will be looked up on the parent node, and then on the
 612  *   parent's parent, and so forth. Note that if the property has a
 613  *   value of 'inherit' it will be inherited even if %FALSE is passed
 614  *   in for @inherit; this only affects the default behavior for inheritance.
 615  * @value: (out): location to store the value that was determined.
 616  *   If the property is not found, the value in this location
 617  *   will not be changed.
 618  *
 619  * Generically looks up a property containing a single numeric value
 620  *  without units.
 621  *
 622  * See also st_theme_node_get_double(), which provides a simpler API.
 623  *
 624  * Return value: %TRUE if the property was found in the properties for this
 625  *  theme node (or in the properties of parent nodes when inheriting.)
 626  */
 627 gboolean
 628 st_theme_node_lookup_double (StThemeNode *node,
 629                              const char  *property_name,
 630                              gboolean     inherit,
 631                              double      *value)
 632 {
 633   gboolean result = FALSE;
 634   int i;
 635 
 636   ensure_properties (node);
 637 
 638   for (i = node->n_properties - 1; i >= 0; i--)
 639     {
 640       CRDeclaration *decl = node->properties[i];
 641 
 642       if (strcmp (decl->property->stryng->str, property_name) == 0)
 643         {
 644           CRTerm *term = decl->value;
 645 
 646           if (term->type != TERM_NUMBER || term->content.num->type != NUM_GENERIC)
 647             continue;
 648 
 649           *value = term->content.num->val;
 650           result = TRUE;
 651           break;
 652         }
 653     }
 654 
 655   if (!result && inherit && node->parent_node)
 656     result = st_theme_node_lookup_double (node->parent_node, property_name, inherit, value);
 657 
 658   return result;
 659 }
 660 
 661 /**
 662  * st_theme_node_get_double:
 663  * @node: a #StThemeNode
 664  * @property_name: The name of the numeric property
 665  *
 666  * Generically looks up a property containing a single numeric value
 667  *  without units.
 668  *
 669  * See also st_theme_node_lookup_double(), which provides more options,
 670  * and lets you handle the case where the theme does not specify the
 671  * indicated value.
 672  *
 673  * Return value: the value found. If @property_name is not
 674  *  found, a warning will be logged and 0 will be returned.
 675  */
 676 gdouble
 677 st_theme_node_get_double (StThemeNode *node,
 678                           const char  *property_name)
 679 {
 680   gdouble value;
 681 
 682   if (st_theme_node_lookup_double (node, property_name, FALSE, &value))
 683     return value;
 684   else
 685     {
 686       g_warning ("Did not find double property '%s'", property_name);
 687       return 0.0;
 688     }
 689 }
 690 
 691 static const PangoFontDescription *
 692 get_parent_font (StThemeNode *node)
 693 {
 694   if (node->parent_node)
 695     return st_theme_node_get_font (node->parent_node);
 696   else
 697     return st_theme_context_get_font (node->context);
 698 }
 699 
 700 static GetFromTermResult
 701 get_length_from_term (StThemeNode *node,
 702                       CRTerm      *term,
 703                       gboolean     use_parent_font,
 704                       gdouble     *length)
 705 {
 706   CRNum *num;
 707 
 708   enum {
 709     ABSOLUTE,
 710     POINTS,
 711     FONT_RELATIVE,
 712   } type = ABSOLUTE;
 713 
 714   double multiplier = 1.0;
 715 
 716   if (term->type != TERM_NUMBER)
 717     {
 718       g_warning ("Ignoring length property that isn't a number");
 719       return VALUE_NOT_FOUND;
 720     }
 721 
 722   num = term->content.num;
 723 
 724   switch (num->type)
 725     {
 726     case NUM_LENGTH_PX:
 727       type = ABSOLUTE;
 728       multiplier = 1;
 729       break;
 730     case NUM_LENGTH_PT:
 731       type = POINTS;
 732       multiplier = 1;
 733       break;
 734     case NUM_LENGTH_IN:
 735       type = POINTS;
 736       multiplier = 72;
 737       break;
 738     case NUM_LENGTH_CM:
 739       type = POINTS;
 740       multiplier = 72. / 2.54;
 741       break;
 742     case NUM_LENGTH_MM:
 743       type = POINTS;
 744       multiplier = 72. / 25.4;
 745       break;
 746     case NUM_LENGTH_PC:
 747       type = POINTS;
 748       multiplier = 12. / 25.4;
 749       break;
 750     case NUM_LENGTH_EM:
 751       {
 752         type = FONT_RELATIVE;
 753         multiplier = 1;
 754         break;
 755       }
 756     case NUM_LENGTH_EX:
 757       {
 758         /* Doing better would require actually resolving the font description
 759          * to a specific font, and Pango doesn't have an ex metric anyways,
 760          * so we'd have to try and synthesize it by complicated means.
 761          *
 762          * The 0.5em is the CSS spec suggested thing to use when nothing
 763          * better is available.
 764          */
 765         type = FONT_RELATIVE;
 766         multiplier = 0.5;
 767         break;
 768       }
 769 
 770     case NUM_INHERIT:
 771       return VALUE_INHERIT;
 772 
 773     case NUM_AUTO:
 774       g_warning ("'auto' not supported for lengths");
 775       return VALUE_NOT_FOUND;
 776 
 777     case NUM_GENERIC:
 778       {
 779         if (num->val != 0)
 780           {
 781             g_warning ("length values must specify a unit");
 782             return VALUE_NOT_FOUND;
 783           }
 784         else
 785           {
 786             type = ABSOLUTE;
 787             multiplier = 0;
 788           }
 789         break;
 790       }
 791 
 792     case NUM_PERCENTAGE:
 793       g_warning ("percentage lengths not currently supported");
 794       return VALUE_NOT_FOUND;
 795 
 796     case NUM_ANGLE_DEG:
 797     case NUM_ANGLE_RAD:
 798     case NUM_ANGLE_GRAD:
 799     case NUM_TIME_MS:
 800     case NUM_TIME_S:
 801     case NUM_FREQ_HZ:
 802     case NUM_FREQ_KHZ:
 803     case NUM_UNKNOWN_TYPE:
 804     case NB_NUM_TYPE:
 805       g_warning ("Ignoring invalid type of number of length property");
 806       return VALUE_NOT_FOUND;
 807     }
 808 
 809   switch (type)
 810     {
 811     case ABSOLUTE:
 812       *length = num->val * multiplier;
 813       break;
 814     case POINTS:
 815       {
 816         double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
 817         *length = num->val * multiplier * (resolution / 72.);
 818       }
 819       break;
 820     case FONT_RELATIVE:
 821       {
 822         const PangoFontDescription *desc;
 823         double font_size;
 824 
 825         if (use_parent_font)
 826           desc = get_parent_font (node);
 827         else
 828           desc = st_theme_node_get_font (node);
 829 
 830         font_size = (double)pango_font_description_get_size (desc) / PANGO_SCALE;
 831 
 832         if (pango_font_description_get_size_is_absolute (desc))
 833           {
 834             *length = num->val * multiplier * font_size;
 835           }
 836         else
 837           {
 838             double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
 839             *length = num->val * multiplier * (resolution / 72.) * font_size;
 840           }
 841       }
 842       break;
 843     default:
 844       g_assert_not_reached ();
 845     }
 846 
 847   return VALUE_FOUND;
 848 }
 849 
 850 static GetFromTermResult
 851 get_length_from_term_int (StThemeNode *node,
 852                           CRTerm      *term,
 853                           gboolean     use_parent_font,
 854                           gint        *length)
 855 {
 856   double value;
 857   GetFromTermResult result;
 858 
 859   result = get_length_from_term (node, term, use_parent_font, &value);
 860   if (result == VALUE_FOUND)
 861     *length = (int) (0.5 + value);
 862   return result;
 863 }
 864 
 865 static GetFromTermResult
 866 get_length_internal (StThemeNode *node,
 867                      const char  *property_name,
 868                      const char  *suffixed,
 869                      gdouble     *length)
 870 {
 871   int i;
 872 
 873   ensure_properties (node);
 874 
 875   for (i = node->n_properties - 1; i >= 0; i--)
 876     {
 877       CRDeclaration *decl = node->properties[i];
 878 
 879       if (strcmp (decl->property->stryng->str, property_name) == 0 ||
 880           (suffixed != NULL && strcmp (decl->property->stryng->str, suffixed) == 0))
 881         {
 882           GetFromTermResult result = get_length_from_term (node, decl->value, FALSE, length);
 883           if (result != VALUE_NOT_FOUND)
 884             return result;
 885         }
 886     }
 887 
 888   return VALUE_NOT_FOUND;
 889 }
 890 
 891 /**
 892  * st_theme_node_lookup_length:
 893  * @node: a #StThemeNode
 894  * @property_name: The name of the length property
 895  * @inherit: if %TRUE, if a value is not found for the property on the
 896  *   node, then it will be looked up on the parent node, and then on the
 897  *   parent's parent, and so forth. Note that if the property has a
 898  *   value of 'inherit' it will be inherited even if %FALSE is passed
 899  *   in for @inherit; this only affects the default behavior for inheritance.
 900  * @length: (out): location to store the length that was determined.
 901  *   If the property is not found, the value in this location
 902  *   will not be changed. The returned length is resolved
 903  *   to pixels.
 904  *
 905  * Generically looks up a property containing a single length value. When
 906  * specific getters (like st_theme_node_get_border_width()) exist, they
 907  * should be used instead. They are cached, so more efficient, and have
 908  * handling for shortcut properties and other details of CSS.
 909  *
 910  * See also st_theme_node_get_length(), which provides a simpler API.
 911  *
 912  * Return value: %TRUE if the property was found in the properties for this
 913  *  theme node (or in the properties of parent nodes when inheriting.)
 914  */
 915 gboolean
 916 st_theme_node_lookup_length (StThemeNode *node,
 917                              const char  *property_name,
 918                              gboolean     inherit,
 919                              gdouble     *length)
 920 {
 921   GetFromTermResult result = get_length_internal (node, property_name, NULL, length);
 922   if (result == VALUE_FOUND)
 923     return TRUE;
 924   else if (result == VALUE_INHERIT)
 925     inherit = TRUE;
 926 
 927   if (inherit && node->parent_node &&
 928       st_theme_node_lookup_length (node->parent_node, property_name, inherit, length))
 929     return TRUE;
 930   else
 931     return FALSE;
 932 }
 933 
 934 /**
 935  * st_theme_node_get_length:
 936  * @node: a #StThemeNode
 937  * @property_name: The name of the length property
 938  *
 939  * Generically looks up a property containing a single length value. When
 940  * specific getters (like st_theme_node_get_border_width()) exist, they
 941  * should be used instead. They are cached, so more efficient, and have
 942  * handling for shortcut properties and other details of CSS.
 943  *
 944  * Unlike st_theme_node_get_color() and st_theme_node_get_double(),
 945  * this does not print a warning if the property is not found; it just
 946  * returns 0.
 947  *
 948  * See also st_theme_node_lookup_length(), which provides more options.
 949  *
 950  * Return value: the length, in pixels, or 0 if the property was not found.
 951  */
 952 gdouble
 953 st_theme_node_get_length (StThemeNode *node,
 954                           const char  *property_name)
 955 {
 956   gdouble length;
 957 
 958   if (st_theme_node_lookup_length (node, property_name, FALSE, &length))
 959     return length;
 960   else
 961     return 0.0;
 962 }
 963 
 964 static void
 965 do_border_radius_term (StThemeNode *node,
 966                        CRTerm      *term,
 967                        gboolean     topleft,
 968                        gboolean     topright,
 969                        gboolean     bottomright,
 970                        gboolean     bottomleft)
 971 {
 972   int value;
 973 
 974   if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND)
 975     return;
 976 
 977   if (topleft)
 978     node->border_radius[ST_CORNER_TOPLEFT] = value;
 979   if (topright)
 980     node->border_radius[ST_CORNER_TOPRIGHT] = value;
 981   if (bottomright)
 982     node->border_radius[ST_CORNER_BOTTOMRIGHT] = value;
 983   if (bottomleft)
 984     node->border_radius[ST_CORNER_BOTTOMLEFT] = value;
 985 }
 986 
 987 static void
 988 do_border_radius (StThemeNode   *node,
 989                   CRDeclaration *decl)
 990 {
 991   const char *property_name = decl->property->stryng->str + 13; /* Skip 'border-radius' */
 992 
 993   if (strcmp (property_name, "") == 0)
 994     {
 995       /* Slight deviation ... if we don't understand some of the terms and understand others,
 996        * then we set the ones we understand and ignore the others instead of ignoring the
 997        * whole thing
 998        */
 999       if (decl->value == NULL) /* 0 values */
1000         return;
1001       else if (decl->value->next == NULL) /* 1 value */
1002         {
1003           do_border_radius_term (node, decl->value,       TRUE, TRUE, TRUE, TRUE); /* all corners */
1004           return;
1005         }
1006       else if (decl->value->next->next == NULL) /* 2 values */
1007         {
1008           do_border_radius_term (node, decl->value,       TRUE,  FALSE,  TRUE,  FALSE);  /* topleft/bottomright */
1009           do_border_radius_term (node, decl->value->next, FALSE,  TRUE,   FALSE, TRUE);  /* topright/bottomleft */
1010         }
1011       else if (decl->value->next->next->next == NULL) /* 3 values */
1012         {
1013           do_border_radius_term (node, decl->value,             TRUE,  FALSE, FALSE, FALSE); /* topleft */
1014           do_border_radius_term (node, decl->value->next,       FALSE, TRUE,  FALSE, TRUE);  /* topright/bottomleft */
1015           do_border_radius_term (node, decl->value->next->next, FALSE, FALSE, TRUE,  FALSE);  /* bottomright */
1016         }
1017       else if (decl->value->next->next->next->next == NULL) /* 4 values */
1018         {
1019           do_border_radius_term (node, decl->value,                   TRUE,  FALSE, FALSE, FALSE); /* topleft */
1020           do_border_radius_term (node, decl->value->next,             FALSE, TRUE,  FALSE, FALSE); /* topright */
1021           do_border_radius_term (node, decl->value->next->next,       FALSE, FALSE, TRUE,  FALSE); /* bottomright */
1022           do_border_radius_term (node, decl->value->next->next->next, FALSE, FALSE, FALSE, TRUE);  /* bottomleft */
1023         }
1024       else
1025         {
1026           g_warning ("Too many values for border-radius property");
1027           return;
1028         }
1029     }
1030   else
1031     {
1032       if (decl->value == NULL || decl->value->next != NULL)
1033         return;
1034 
1035       if (strcmp (property_name, "-topleft") == 0)
1036         do_border_radius_term (node, decl->value, TRUE,  FALSE, FALSE, FALSE);
1037       else if (strcmp (property_name, "-topright") == 0)
1038         do_border_radius_term (node, decl->value, FALSE, TRUE,  FALSE, FALSE);
1039       else if (strcmp (property_name, "-bottomright") == 0)
1040         do_border_radius_term (node, decl->value, FALSE, FALSE, TRUE,  FALSE);
1041       else if (strcmp (property_name, "-bottomleft") == 0)
1042         do_border_radius_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
1043     }
1044 }
1045 
1046 static void
1047 do_border_property (StThemeNode   *node,
1048                     CRDeclaration *decl)
1049 {
1050   const char *property_name = decl->property->stryng->str + 6; /* Skip 'border' */
1051   StSide side = (StSide)-1;
1052   ClutterColor color;
1053   gboolean color_set = FALSE;
1054   int width = 0; /* suppress warning */
1055   gboolean width_set = FALSE;
1056   int j;
1057 
1058   if (g_str_has_prefix (property_name, "-radius"))
1059     {
1060       do_border_radius (node, decl);
1061       return;
1062     }
1063 
1064   if (g_str_has_prefix (property_name, "-left"))
1065     {
1066       side = ST_SIDE_LEFT;
1067       property_name += 5;
1068     }
1069   else if (g_str_has_prefix (property_name, "-right"))
1070     {
1071       side = ST_SIDE_RIGHT;
1072       property_name += 6;
1073     }
1074   else if (g_str_has_prefix (property_name, "-top"))
1075     {
1076       side = ST_SIDE_TOP;
1077       property_name += 4;
1078     }
1079   else if (g_str_has_prefix (property_name, "-bottom"))
1080     {
1081       side = ST_SIDE_BOTTOM;
1082       property_name += 7;
1083     }
1084 
1085   if (strcmp (property_name, "") == 0)
1086     {
1087       /* Set value for width/color/style in any order */
1088       CRTerm *term;
1089 
1090       for (term = decl->value; term; term = term->next)
1091         {
1092           GetFromTermResult result;
1093 
1094           if (term->type == TERM_IDENT)
1095             {
1096               const char *ident = term->content.str->stryng->str;
1097               if (strcmp (ident, "none") == 0 || strcmp (ident, "hidden") == 0)
1098                 {
1099                   width = 0;
1100                   width_set = TRUE;
1101                   continue;
1102                 }
1103               else if (strcmp (ident, "solid") == 0)
1104                 {
1105                   /* The only thing we support */
1106                   continue;
1107                 }
1108               else if (strcmp (ident, "dotted") == 0 ||
1109                        strcmp (ident, "dashed") == 0 ||
1110                        strcmp (ident, "double") == 0 ||
1111                        strcmp (ident, "groove") == 0 ||
1112                        strcmp (ident, "ridge") == 0 ||
1113                        strcmp (ident, "inset") == 0 ||
1114                        strcmp (ident, "outset") == 0)
1115                 {
1116                   /* Treat the same as solid */
1117                   continue;
1118                 }
1119 
1120               /* Presumably a color, fall through */
1121             }
1122 
1123           if (term->type == TERM_NUMBER)
1124             {
1125               result = get_length_from_term_int (node, term, FALSE, &width);
1126               if (result != VALUE_NOT_FOUND)
1127                 {
1128                   width_set = result == VALUE_FOUND;
1129                   continue;
1130                 }
1131             }
1132 
1133           result = get_color_from_term (node, term, &color);
1134           if (result != VALUE_NOT_FOUND)
1135             {
1136               color_set = result == VALUE_FOUND;
1137               continue;
1138             }
1139         }
1140 
1141     }
1142   else if (strcmp (property_name, "-color") == 0)
1143     {
1144       if (decl->value == NULL || decl->value->next != NULL)
1145         return;
1146 
1147       if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
1148         /* Ignore inherit */
1149         color_set = TRUE;
1150     }
1151   else if (strcmp (property_name, "-width") == 0)
1152     {
1153       if (decl->value == NULL || decl->value->next != NULL)
1154         return;
1155 
1156       if (get_length_from_term_int (node, decl->value, FALSE, &width) == VALUE_FOUND)
1157         /* Ignore inherit */
1158         width_set = TRUE;
1159     }
1160 
1161   if (side == (StSide)-1)
1162     {
1163       for (j = 0; j < 4; j++)
1164         {
1165           if (color_set)
1166             node->border_color[j] = color;
1167           if (width_set)
1168             node->border_width[j] = width;
1169         }
1170     }
1171   else
1172     {
1173       if (color_set)
1174         node->border_color[side] = color;
1175       if (width_set)
1176         node->border_width[side] = width;
1177     }
1178 }
1179 
1180 static void
1181 do_outline_property (StThemeNode   *node,
1182                      CRDeclaration *decl)
1183 {
1184   const char *property_name = decl->property->stryng->str + 7; /* Skip 'outline' */
1185   ClutterColor color;
1186   gboolean color_set = FALSE;
1187   int width = 0; /* suppress warning */
1188   gboolean width_set = FALSE;
1189 
1190   if (strcmp (property_name, "") == 0)
1191     {
1192       /* Set value for width/color/style in any order */
1193       CRTerm *term;
1194 
1195       for (term = decl->value; term; term = term->next)
1196         {
1197           GetFromTermResult result;
1198 
1199           if (term->type == TERM_IDENT)
1200             {
1201               const char *ident = term->content.str->stryng->str;
1202               if (strcmp (ident, "none") == 0 || strcmp (ident, "hidden") == 0)
1203                 {
1204                   width = 0;
1205                   width_set = TRUE;
1206                   continue;
1207                 }
1208               else if (strcmp (ident, "solid") == 0)
1209                 {
1210                   /* The only thing we support */
1211                   continue;
1212                 }
1213               else if (strcmp (ident, "dotted") == 0 ||
1214                        strcmp (ident, "dashed") == 0 ||
1215                        strcmp (ident, "double") == 0 ||
1216                        strcmp (ident, "groove") == 0 ||
1217                        strcmp (ident, "ridge") == 0 ||
1218                        strcmp (ident, "inset") == 0 ||
1219                        strcmp (ident, "outset") == 0)
1220                 {
1221                   /* Treat the same as solid */
1222                   continue;
1223                 }
1224 
1225               /* Presumably a color, fall through */
1226             }
1227 
1228           if (term->type == TERM_NUMBER)
1229             {
1230               result = get_length_from_term_int (node, term, FALSE, &width);
1231               if (result != VALUE_NOT_FOUND)
1232                 {
1233                   width_set = result == VALUE_FOUND;
1234                   continue;
1235                 }
1236             }
1237 
1238           result = get_color_from_term (node, term, &color);
1239           if (result != VALUE_NOT_FOUND)
1240             {
1241               color_set = result == VALUE_FOUND;
1242               continue;
1243             }
1244         }
1245 
1246     }
1247   else if (strcmp (property_name, "-color") == 0)
1248     {
1249       if (decl->value == NULL || decl->value->next != NULL)
1250         return;
1251 
1252       if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
1253         /* Ignore inherit */
1254         color_set = TRUE;
1255     }
1256   else if (strcmp (property_name, "-width") == 0)
1257     {
1258       if (decl->value == NULL || decl->value->next != NULL)
1259         return;
1260 
1261       if (get_length_from_term_int (node, decl->value, FALSE, &width) == VALUE_FOUND)
1262         /* Ignore inherit */
1263         width_set = TRUE;
1264     }
1265 
1266   if (color_set)
1267     node->outline_color = color;
1268   if (width_set)
1269     node->outline_width = width;
1270 }
1271 
1272 static void
1273 do_padding_property_term (StThemeNode *node,
1274                           CRTerm      *term,
1275                           gboolean     left,
1276                           gboolean     right,
1277                           gboolean     top,
1278                           gboolean     bottom)
1279 {
1280   int value;
1281 
1282   if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND)
1283     return;
1284 
1285   if (left)
1286     node->padding[ST_SIDE_LEFT] = value;
1287   if (right)
1288     node->padding[ST_SIDE_RIGHT] = value;
1289   if (top)
1290     node->padding[ST_SIDE_TOP] = value;
1291   if (bottom)
1292     node->padding[ST_SIDE_BOTTOM] = value;
1293 }
1294 
1295 static void
1296 do_padding_property (StThemeNode   *node,
1297                      CRDeclaration *decl)
1298 {
1299   const char *property_name = decl->property->stryng->str + 7; /* Skip 'padding' */
1300 
1301   if (strcmp (property_name, "") == 0)
1302     {
1303       /* Slight deviation ... if we don't understand some of the terms and understand others,
1304        * then we set the ones we understand and ignore the others instead of ignoring the
1305        * whole thing
1306        */
1307       if (decl->value == NULL) /* 0 values */
1308         return;
1309       else if (decl->value->next == NULL) /* 1 value */
1310         {
1311           do_padding_property_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* left/right/top/bottom */
1312           return;
1313         }
1314       else if (decl->value->next->next == NULL) /* 2 values */
1315         {
1316           do_padding_property_term (node, decl->value,       FALSE, FALSE, TRUE,  TRUE);  /* top/bottom */
1317           do_padding_property_term (node, decl->value->next, TRUE, TRUE,   FALSE, FALSE); /* left/right */
1318         }
1319       else if (decl->value->next->next->next == NULL) /* 3 values */
1320         {
1321           do_padding_property_term (node, decl->value,             FALSE, FALSE, TRUE,  FALSE); /* top */
1322           do_padding_property_term (node, decl->value->next,       TRUE,  TRUE,  FALSE, FALSE); /* left/right */
1323           do_padding_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE);  /* bottom */
1324         }
1325       else if (decl->value->next->next->next->next == NULL) /* 4 values */
1326         {
1327           do_padding_property_term (node, decl->value,                   FALSE, FALSE, TRUE,  FALSE); /* top */
1328           do_padding_property_term (node, decl->value->next,             FALSE, TRUE,  FALSE, FALSE); /* right */
1329           do_padding_property_term (node, decl->value->next->next,       FALSE, FALSE, FALSE, TRUE);  /* bottom */
1330           do_padding_property_term (node, decl->value->next->next->next, TRUE,  FALSE, FALSE, FALSE); /* left */
1331         }
1332       else
1333         {
1334           g_warning ("Too many values for padding property");
1335           return;
1336         }
1337     }
1338   else
1339     {
1340       if (decl->value == NULL || decl->value->next != NULL)
1341         return;
1342 
1343       if (strcmp (property_name, "-left") == 0)
1344         do_padding_property_term (node, decl->value, TRUE,  FALSE, FALSE, FALSE);
1345       else if (strcmp (property_name, "-right") == 0)
1346         do_padding_property_term (node, decl->value, FALSE, TRUE,  FALSE, FALSE);
1347       else if (strcmp (property_name, "-top") == 0)
1348         do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE,  FALSE);
1349       else if (strcmp (property_name, "-bottom") == 0)
1350         do_padding_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
1351     }
1352 }
1353 
1354 static void
1355 do_size_property (StThemeNode   *node,
1356                   CRDeclaration *decl,
1357                   int           *node_value)
1358 {
1359   get_length_from_term_int (node, decl->value, FALSE, node_value);
1360 }
1361 
1362 void
1363 _st_theme_node_ensure_geometry (StThemeNode *node)
1364 {
1365   int i, j;
1366 
1367   if (node->geometry_computed)
1368     return;
1369 
1370   node->geometry_computed = TRUE;
1371 
1372   ensure_properties (node);
1373 
1374   for (j = 0; j < 4; j++)
1375     {
1376       node->border_width[j] = 0;
1377       node->border_color[j] = TRANSPARENT_COLOR;
1378     }
1379 
1380   node->outline_width = 0;
1381   node->outline_color = TRANSPARENT_COLOR;
1382 
1383   node->width = -1;
1384   node->height = -1;
1385   node->min_width = -1;
1386   node->min_height = -1;
1387   node->max_width = -1;
1388   node->max_height = -1;
1389 
1390   for (i = 0; i < node->n_properties; i++)
1391     {
1392       CRDeclaration *decl = node->properties[i];
1393       const char *property_name = decl->property->stryng->str;
1394 
1395       if (g_str_has_prefix (property_name, "border"))
1396         do_border_property (node, decl);
1397       else if (g_str_has_prefix (property_name, "outline"))
1398         do_outline_property (node, decl);
1399       else if (g_str_has_prefix (property_name, "padding"))
1400         do_padding_property (node, decl);
1401       else if (strcmp (property_name, "width") == 0)
1402         do_size_property (node, decl, &node->width);
1403       else if (strcmp (property_name, "height") == 0)
1404         do_size_property (node, decl, &node->height);
1405       else if (strcmp (property_name, "min-width") == 0)
1406         do_size_property (node, decl, &node->min_width);
1407       else if (strcmp (property_name, "min-height") == 0)
1408         do_size_property (node, decl, &node->min_height);
1409       else if (strcmp (property_name, "max-width") == 0)
1410         do_size_property (node, decl, &node->max_width);
1411       else if (strcmp (property_name, "max-height") == 0)
1412         do_size_property (node, decl, &node->max_height);
1413     }
1414 
1415   if (node->width != -1)
1416     {
1417       if (node->min_width == -1)
1418         node->min_width = node->width;
1419       else if (node->width < node->min_width)
1420         node->width = node->min_width;
1421       if (node->max_width == -1)
1422         node->max_width = node->width;
1423       else if (node->width > node->max_width)
1424         node->width = node->max_width;
1425     }
1426 
1427   if (node->height != -1)
1428     {
1429       if (node->min_height == -1)
1430         node->min_height = node->height;
1431       else if (node->height < node->min_height)
1432         node->height = node->min_height;
1433       if (node->max_height == -1)
1434         node->max_height = node->height;
1435       else if (node->height > node->max_height)
1436         node->height = node->max_height;
1437     }
1438 }
1439 
1440 int
1441 st_theme_node_get_border_width (StThemeNode *node,
1442                                 StSide       side)
1443 {
1444   g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
1445   g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
1446 
1447   _st_theme_node_ensure_geometry (node);
1448 
1449   return node->border_width[side];
1450 }
1451 
1452 int
1453 st_theme_node_get_border_radius (StThemeNode *node,
1454                                  StCorner     corner)
1455 {
1456   g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
1457   g_return_val_if_fail (corner >= ST_CORNER_TOPLEFT && corner <= ST_CORNER_BOTTOMLEFT, 0.);
1458 
1459   _st_theme_node_ensure_geometry (node);
1460 
1461   return node->border_radius[corner];
1462 }
1463 
1464 int
1465 st_theme_node_get_outline_width (StThemeNode  *node)
1466 {
1467   g_return_val_if_fail (ST_IS_THEME_NODE (node), 0);
1468 
1469   _st_theme_node_ensure_geometry (node);
1470 
1471   return node->outline_width;
1472 }
1473 
1474 /**
1475  * st_theme_node_get_outline_color:
1476  * @node: a #StThemeNode
1477  * @color: (out caller-allocates): location to store the color
1478  *
1479  * Gets the color of @node's outline.
1480  */
1481 void
1482 st_theme_node_get_outline_color (StThemeNode  *node,
1483                                  ClutterColor *color)
1484 {
1485   g_return_if_fail (ST_IS_THEME_NODE (node));
1486 
1487   _st_theme_node_ensure_geometry (node);
1488 
1489   *color = node->outline_color;
1490 }
1491 
1492 int
1493 st_theme_node_get_width (StThemeNode *node)
1494 {
1495   g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1496 
1497   _st_theme_node_ensure_geometry (node);
1498   return node->width;
1499 }
1500 
1501 int
1502 st_theme_node_get_height (StThemeNode *node)
1503 {
1504   g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1505 
1506   _st_theme_node_ensure_geometry (node);
1507   return node->height;
1508 }
1509 
1510 int
1511 st_theme_node_get_min_width (StThemeNode *node)
1512 {
1513   g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1514 
1515   _st_theme_node_ensure_geometry (node);
1516   return node->min_width;
1517 }
1518 
1519 int
1520 st_theme_node_get_min_height (StThemeNode *node)
1521 {
1522   g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1523 
1524   _st_theme_node_ensure_geometry (node);
1525   return node->min_height;
1526 }
1527 
1528 int
1529 st_theme_node_get_max_width (StThemeNode *node)
1530 {
1531   g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1532 
1533   _st_theme_node_ensure_geometry (node);
1534   return node->max_width;
1535 }
1536 
1537 int
1538 st_theme_node_get_max_height (StThemeNode *node)
1539 {
1540   g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1541 
1542   _st_theme_node_ensure_geometry (node);
1543   return node->max_height;
1544 }
1545 
1546 static GetFromTermResult
1547 get_background_color_from_term (StThemeNode  *node,
1548                                 CRTerm       *term,
1549                                 ClutterColor *color)
1550 {
1551   GetFromTermResult result = get_color_from_term (node, term, color);
1552   if (result == VALUE_NOT_FOUND)
1553     {
1554       if (term_is_transparent (term))
1555         {
1556           *color = TRANSPARENT_COLOR;
1557           return VALUE_FOUND;
1558         }
1559     }
1560 
1561   return result;
1562 }
1563 
1564 void
1565 _st_theme_node_ensure_background (StThemeNode *node)
1566 {
1567   int i;
1568 
1569   if (node->background_computed)
1570     return;
1571 
1572   node->background_repeat = FALSE;
1573   node->background_computed = TRUE;
1574   node->background_color = TRANSPARENT_COLOR;
1575   node->background_gradient_type = ST_GRADIENT_NONE;
1576   node->background_position_set = FALSE;
1577   node->background_size = ST_BACKGROUND_SIZE_AUTO;
1578 
1579   ensure_properties (node);
1580 
1581   for (i = 0; i < node->n_properties; i++)
1582     {
1583       CRDeclaration *decl = node->properties[i];
1584       const char *property_name = decl->property->stryng->str;
1585 
1586       if (g_str_has_prefix (property_name, "background"))
1587         property_name += 10;
1588       else
1589         continue;
1590 
1591       if (strcmp (property_name, "") == 0)
1592         {
1593           /* We're very liberal here ... if we recognize any term in the expression we take it, and
1594            * we ignore the rest. The actual specification is:
1595            *
1596            * background: [<'background-color'> || <'background-image'> || <'background-repeat'> || <'background-attachment'> || <'background-position'>] | inherit
1597            */
1598 
1599           CRTerm *term;
1600           /* background: property sets all terms to specified or default values */
1601           node->background_color = TRANSPARENT_COLOR;
1602           g_free (node->background_image);
1603           node->background_image = NULL;
1604           node->background_position_set = FALSE;
1605           node->background_size = ST_BACKGROUND_SIZE_AUTO;
1606 
1607           for (term = decl->value; term; term = term->next)
1608             {
1609               GetFromTermResult result = get_background_color_from_term (node, term, &node->background_color);
1610               if (result == VALUE_FOUND)
1611                 {
1612                   /* color stored in node->background_color */
1613                 }
1614               else if (result == VALUE_INHERIT)
1615                 {
1616                   if (node->parent_node)
1617                     {
1618                       st_theme_node_get_background_color (node->parent_node, &node->background_color);
1619                       node->background_image = g_strdup (st_theme_node_get_background_image (node->parent_node));
1620                     }
1621                 }
1622               else if (term_is_none (term))
1623                 {
1624                   /* leave node->background_color as transparent */
1625                 }
1626               else if (term->type == TERM_URI)
1627                 {
1628                   CRStyleSheet *base_stylesheet;
1629 
1630                   if (decl->parent_statement != NULL)
1631                     base_stylesheet = decl->parent_statement->parent_sheet;
1632                   else
1633                     base_stylesheet = NULL;
1634 
1635                   node->background_image = _st_theme_resolve_url (node->theme,
1636                                                                   base_stylesheet,
1637                                                                   term->content.str->stryng->str);
1638                 }
1639             }
1640         }
1641       else if (strcmp (property_name, "-position") == 0)
1642         {
1643           GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_position_x);
1644           if (result == VALUE_NOT_FOUND)
1645             {
1646               node->background_position_set = FALSE;
1647               continue;
1648             }
1649           else
1650             node->background_position_set = TRUE;
1651 
1652           result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_position_y);
1653 
1654           if (result == VALUE_NOT_FOUND)
1655             {
1656               node->background_position_set = FALSE;
1657               continue;
1658             }
1659           else
1660             node->background_position_set = TRUE;
1661         }
1662       else if (strcmp (property_name, "-repeat") == 0)
1663         {
1664           if (decl->value->type == TERM_IDENT)
1665             {
1666               if (strcmp (decl->value->content.str->stryng->str, "repeat") == 0)
1667                 node->background_repeat = TRUE;
1668             }
1669         }
1670       else if (strcmp (property_name, "-size") == 0)
1671         {
1672           if (decl->value->type == TERM_IDENT)
1673             {
1674               if (strcmp (decl->value->content.str->stryng->str, "contain") == 0)
1675                 node->background_size = ST_BACKGROUND_SIZE_CONTAIN;
1676               else if (strcmp (decl->value->content.str->stryng->str, "cover") == 0)
1677                 node->background_size = ST_BACKGROUND_SIZE_COVER;
1678               else if ((strcmp (decl->value->content.str->stryng->str, "auto") == 0) && (decl->value->next) && (decl->value->next->type == TERM_NUMBER))
1679                 {
1680                   GetFromTermResult result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h);
1681 
1682                   node->background_size_w = -1;
1683                   node->background_size = (result == VALUE_FOUND) ? ST_BACKGROUND_SIZE_FIXED : ST_BACKGROUND_SIZE_AUTO;
1684                 }
1685               else
1686                 node->background_size = ST_BACKGROUND_SIZE_AUTO;
1687             }
1688           else if (decl->value->type == TERM_NUMBER)
1689             {
1690               GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_size_w);
1691               if (result == VALUE_NOT_FOUND)
1692                 continue;
1693 
1694               node->background_size = ST_BACKGROUND_SIZE_FIXED;
1695 
1696               if ((decl->value->next) && (decl->value->next->type == TERM_NUMBER))
1697                 {
1698                   result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h);
1699 
1700                   if (result == VALUE_FOUND)
1701                     continue;
1702                 }
1703               node->background_size_h = -1;
1704             }
1705           else
1706             node->background_size = ST_BACKGROUND_SIZE_AUTO;
1707         }
1708       else if (strcmp (property_name, "-color") == 0)
1709         {
1710           GetFromTermResult result;
1711 
1712           if (decl->value == NULL || decl->value->next != NULL)
1713             continue;
1714 
1715           result = get_background_color_from_term (node, decl->value, &node->background_color);
1716           if (result == VALUE_FOUND)
1717             {
1718               /* color stored in node->background_color */
1719             }
1720           else if (result == VALUE_INHERIT)
1721             {
1722               if (node->parent_node)
1723                 st_theme_node_get_background_color (node->parent_node, &node->background_color);
1724             }
1725         }
1726       else if (strcmp (property_name, "-image") == 0)
1727         {
1728           if (decl->value == NULL || decl->value->next != NULL)
1729             continue;
1730 
1731           if (decl->value->type == TERM_URI)
1732             {
1733               CRStyleSheet *base_stylesheet;
1734 
1735               if (decl->parent_statement != NULL)
1736                 base_stylesheet = decl->parent_statement->parent_sheet;
1737               else
1738                 base_stylesheet = NULL;
1739 
1740               g_free (node->background_image);
1741               node->background_image = _st_theme_resolve_url (node->theme,
1742                                                               base_stylesheet,
1743                                                               decl->value->content.str->stryng->str);
1744             }
1745           else if (term_is_inherit (decl->value))
1746             {
1747               g_free (node->background_image);
1748               node->background_image = g_strdup (st_theme_node_get_background_image (node->parent_node));
1749             }
1750           else if (term_is_none (decl->value))
1751             {
1752               g_free (node->background_image);
1753               node->background_image = NULL;
1754             }
1755         }
1756       else if (strcmp (property_name, "-gradient-direction") == 0)
1757         {
1758           CRTerm *term = decl->value;
1759           if (strcmp (term->content.str->stryng->str, "vertical") == 0)
1760             {
1761               node->background_gradient_type = ST_GRADIENT_VERTICAL;
1762             }
1763           else if (strcmp (term->content.str->stryng->str, "horizontal") == 0)
1764             {
1765               node->background_gradient_type = ST_GRADIENT_HORIZONTAL;
1766             }
1767           else if (strcmp (term->content.str->stryng->str, "radial") == 0)
1768             {
1769               node->background_gradient_type = ST_GRADIENT_RADIAL;
1770             }
1771           else if (strcmp (term->content.str->stryng->str, "none") == 0)
1772             {
1773               node->background_gradient_type = ST_GRADIENT_NONE;
1774             }
1775           else
1776             {
1777               g_warning ("Unrecognized background-gradient-direction \"%s\"",
1778                          term->content.str->stryng->str);
1779             }
1780         }
1781       else if (strcmp (property_name, "-gradient-start") == 0)
1782         {
1783           get_color_from_term (node, decl->value, &node->background_color);
1784         }
1785       else if (strcmp (property_name, "-gradient-end") == 0)
1786         {
1787           get_color_from_term (node, decl->value, &node->background_gradient_end);
1788         }
1789     }
1790 }
1791 
1792 /**
1793  * st_theme_node_get_background_color:
1794  * @node: a #StThemeNode
1795  * @color: (out caller-allocates): location to store the color
1796  *
1797  * Gets @node's background color.
1798  */
1799 void
1800 st_theme_node_get_background_color (StThemeNode  *node,
1801                                     ClutterColor *color)
1802 {
1803   g_return_if_fail (ST_IS_THEME_NODE (node));
1804 
1805   _st_theme_node_ensure_background (node);
1806 
1807   *color = node->background_color;
1808 }
1809 
1810 const char *
1811 st_theme_node_get_background_image (StThemeNode *node)
1812 {
1813   g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
1814 
1815   _st_theme_node_ensure_background (node);
1816 
1817   return node->background_image;
1818 }
1819 
1820 /**
1821  * st_theme_node_get_foreground_color:
1822  * @node: a #StThemeNode
1823  * @color: (out caller-allocates): location to store the color
1824  *
1825  * Gets @node's foreground color.
1826  */
1827 void
1828 st_theme_node_get_foreground_color (StThemeNode  *node,
1829                                     ClutterColor *color)
1830 {
1831   g_return_if_fail (ST_IS_THEME_NODE (node));
1832 
1833   if (!node->foreground_computed)
1834     {
1835       int i;
1836 
1837       node->foreground_computed = TRUE;
1838 
1839       ensure_properties (node);
1840 
1841       for (i = node->n_properties - 1; i >= 0; i--)
1842         {
1843           CRDeclaration *decl = node->properties[i];
1844 
1845           if (strcmp (decl->property->stryng->str, "color") == 0)
1846             {
1847               GetFromTermResult result = get_color_from_term (node, decl->value, &node->foreground_color);
1848               if (result == VALUE_FOUND)
1849                 goto out;
1850               else if (result == VALUE_INHERIT)
1851                 break;
1852             }
1853         }
1854 
1855       if (node->parent_node)
1856         st_theme_node_get_foreground_color (node->parent_node, &node->foreground_color);
1857       else
1858         node->foreground_color = BLACK_COLOR; /* default to black */
1859     }
1860 
1861  out:
1862   *color = node->foreground_color;
1863 }
1864 
1865 
1866 /**
1867  * st_theme_node_get_background_gradient:
1868  * @node: A #StThemeNode
1869  * @type: (out): Type of gradient
1870  * @start: (out caller-allocates): Color at start of gradient
1871  * @end: (out caller-allocates): Color at end of gradient
1872  *
1873  * The @start and @end arguments will only be set if @type is not #ST_GRADIENT_NONE.
1874  */
1875 void
1876 st_theme_node_get_background_gradient (StThemeNode    *node,
1877                                        StGradientType *type,
1878                                        ClutterColor   *start,
1879                                        ClutterColor   *end)
1880 {
1881   g_return_if_fail (ST_IS_THEME_NODE (node));
1882 
1883   _st_theme_node_ensure_background (node);
1884 
1885   *type = node->background_gradient_type;
1886   if (*type != ST_GRADIENT_NONE)
1887     {
1888       *start = node->background_color;
1889       *end = node->background_gradient_end;
1890     }
1891 }
1892 
1893 /**
1894  * st_theme_node_get_border_color:
1895  * @node: a #StThemeNode
1896  * @side: a #StSide
1897  * @color: (out caller-allocates): location to store the color
1898  *
1899  * Gets the color of @node's border on @side
1900  */
1901 void
1902 st_theme_node_get_border_color (StThemeNode  *node,
1903                                 StSide        side,
1904                                 ClutterColor *color)
1905 {
1906   g_return_if_fail (ST_IS_THEME_NODE (node));
1907   g_return_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT);
1908 
1909   _st_theme_node_ensure_geometry (node);
1910 
1911   *color = node->border_color[side];
1912 }
1913 
1914 double
1915 st_theme_node_get_padding (StThemeNode *node,
1916                            StSide       side)
1917 {
1918   g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
1919   g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
1920 
1921   _st_theme_node_ensure_geometry (node);
1922 
1923   return node->padding[side];
1924 }
1925 
1926 /**
1927  * st_theme_node_get_transition_duration:
1928  * @node: an #StThemeNode
1929  *
1930  * Get the value of the transition-duration property, which
1931  * specifies the transition time between the previous #StThemeNode
1932  * and @node.
1933  *
1934  * Returns: the node's transition duration in milliseconds
1935  */
1936 int
1937 st_theme_node_get_transition_duration (StThemeNode *node)
1938 {
1939   gdouble value = 0.0;
1940 
1941   g_return_val_if_fail (ST_IS_THEME_NODE (node), 0);
1942 
1943   if (node->transition_duration > -1)
1944     return st_slow_down_factor * node->transition_duration;
1945 
1946   st_theme_node_lookup_double (node, "transition-duration", FALSE, &value);
1947 
1948   node->transition_duration = (int)value;
1949 
1950   return st_slow_down_factor * node->transition_duration;
1951 }
1952 
1953 StTextDecoration
1954 st_theme_node_get_text_decoration (StThemeNode *node)
1955 {
1956   int i;
1957 
1958   ensure_properties (node);
1959 
1960   for (i = node->n_properties - 1; i >= 0; i--)
1961     {
1962       CRDeclaration *decl = node->properties[i];
1963 
1964       if (strcmp (decl->property->stryng->str, "text-decoration") == 0)
1965         {
1966           CRTerm *term = decl->value;
1967           StTextDecoration decoration = 0;
1968 
1969           /* Specification is none | [ underline || overline || line-through || blink ] | inherit
1970            *
1971            * We're a bit more liberal, and for example treat 'underline none' as the same as
1972            * none.
1973            */
1974           for (; term; term = term->next)
1975             {
1976               if (term->type != TERM_IDENT)
1977                 goto next_decl;
1978 
1979               if (strcmp (term->content.str->stryng->str, "none") == 0)
1980                 {
1981                   return 0;
1982                 }
1983               else if (strcmp (term->content.str->stryng->str, "inherit") == 0)
1984                 {
1985                   if (node->parent_node)
1986                     return st_theme_node_get_text_decoration (node->parent_node);
1987                 }
1988               else if (strcmp (term->content.str->stryng->str, "underline") == 0)
1989                 {
1990                   decoration |= ST_TEXT_DECORATION_UNDERLINE;
1991                 }
1992               else if (strcmp (term->content.str->stryng->str, "overline") == 0)
1993                 {
1994                   decoration |= ST_TEXT_DECORATION_OVERLINE;
1995                 }
1996               else if (strcmp (term->content.str->stryng->str, "line-through") == 0)
1997                 {
1998                   decoration |= ST_TEXT_DECORATION_LINE_THROUGH;
1999                 }
2000               else if (strcmp (term->content.str->stryng->str, "blink") == 0)
2001                 {
2002                   decoration |= ST_TEXT_DECORATION_BLINK;
2003                 }
2004               else
2005                 {
2006                   goto next_decl;
2007                 }
2008             }
2009 
2010           return decoration;
2011         }
2012 
2013     next_decl:
2014       ;
2015     }
2016 
2017   return 0;
2018 }
2019 
2020 StTextAlign
2021 st_theme_node_get_text_align(StThemeNode *node)
2022 {
2023   int i;
2024 
2025   ensure_properties(node);
2026 
2027   for (i = node->n_properties - 1; i >= 0; i--)
2028     {
2029       CRDeclaration *decl = node->properties[i];
2030 
2031       if (strcmp(decl->property->stryng->str, "text-align") == 0)
2032         {
2033           CRTerm *term = decl->value;
2034 
2035           if (term->type != TERM_IDENT || term->next)
2036             continue;
2037 
2038           if (strcmp(term->content.str->stryng->str, "inherit") == 0)
2039             {
2040               if (node->parent_node)
2041                 return st_theme_node_get_text_align(node->parent_node);
2042               return ST_TEXT_ALIGN_LEFT;
2043             }
2044           else if (strcmp(term->content.str->stryng->str, "left") == 0)
2045             {
2046               return ST_TEXT_ALIGN_LEFT;
2047             }
2048           else if (strcmp(term->content.str->stryng->str, "right") == 0)
2049             {
2050               return ST_TEXT_ALIGN_RIGHT;
2051             }
2052           else if (strcmp(term->content.str->stryng->str, "center") == 0)
2053             {
2054               return ST_TEXT_ALIGN_CENTER;
2055             }
2056           else if (strcmp(term->content.str->stryng->str, "justify") == 0)
2057             {
2058               return ST_TEXT_ALIGN_JUSTIFY;
2059             }
2060         }
2061     }
2062   if(node->parent_node)
2063     return st_theme_node_get_text_align(node->parent_node);
2064   return ST_TEXT_ALIGN_LEFT;
2065 }
2066 
2067 static gboolean
2068 font_family_from_terms (CRTerm *term,
2069                         char  **family)
2070 {
2071   GString *family_string;
2072   gboolean result = FALSE;
2073   gboolean last_was_quoted = FALSE;
2074 
2075   if (!term)
2076     return FALSE;
2077 
2078   family_string = g_string_new (NULL);
2079 
2080   while (term)
2081     {
2082       if (term->type != TERM_STRING && term->type != TERM_IDENT)
2083         {
2084           goto out;
2085         }
2086 
2087       if (family_string->len > 0)
2088         {
2089           if (term->the_operator != COMMA && term->the_operator != NO_OP)
2090             goto out;
2091           /* Can concatenate two bare words, but not two quoted strings */
2092           if ((term->the_operator == NO_OP && last_was_quoted) || term->type == TERM_STRING)
2093             goto out;
2094 
2095           if (term->the_operator == NO_OP)
2096             g_string_append (family_string, " ");
2097           else
2098             g_string_append (family_string, ", ");
2099         }
2100       else
2101         {
2102           if (term->the_operator != NO_OP)
2103             goto out;
2104         }
2105 
2106       g_string_append (family_string, term->content.str->stryng->str);
2107 
2108       term = term->next;
2109     }
2110 
2111   result = TRUE;
2112 
2113  out:
2114   if (result)
2115     {
2116       *family = g_string_free (family_string, FALSE);
2117       return TRUE;
2118     }
2119   else
2120     {
2121       *family = g_string_free (family_string, TRUE);
2122       return FALSE;
2123     }
2124 }
2125 
2126 /* In points */
2127 static int font_sizes[] = {
2128   6 * 1024,   /* xx-small */
2129   8 * 1024,   /* x-small */
2130   10 * 1024,  /* small */
2131   12 * 1024,  /* medium */
2132   16 * 1024,  /* large */
2133   20 * 1024,  /* x-large */
2134   24 * 1024,  /* xx-large */
2135 };
2136 
2137 static gboolean
2138 font_size_from_term (StThemeNode *node,
2139                      CRTerm      *term,
2140                      double      *size)
2141 {
2142   if (term->type == TERM_IDENT)
2143     {
2144       double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
2145       /* We work in integers to avoid double comparisons when converting back
2146        * from a size in pixels to a logical size.
2147        */
2148       int size_points = (int)(0.5 + *size * (72. / resolution));
2149 
2150       if (strcmp (term->content.str->stryng->str, "xx-small") == 0)
2151         size_points = font_sizes[0];
2152       else if (strcmp (term->content.str->stryng->str, "x-small") == 0)
2153         size_points = font_sizes[1];
2154       else if (strcmp (term->content.str->stryng->str, "small") == 0)
2155         size_points = font_sizes[2];
2156       else if (strcmp (term->content.str->stryng->str, "medium") == 0)
2157         size_points = font_sizes[3];
2158       else if (strcmp (term->content.str->stryng->str, "large") == 0)
2159         size_points = font_sizes[4];
2160       else if (strcmp (term->content.str->stryng->str, "x-large") == 0)
2161         size_points = font_sizes[5];
2162       else if (strcmp (term->content.str->stryng->str, "xx-large") == 0)
2163         size_points = font_sizes[6];
2164       else if (strcmp (term->content.str->stryng->str, "smaller") == 0)
2165         {
2166           /* Find the standard size equal to or smaller than the current size */
2167           int i = 0;
2168 
2169           while (i <= 6 && font_sizes[i] < size_points)
2170             i++;
2171 
2172           if (i > 6)
2173             {
2174               /* original size greater than any standard size */
2175               size_points = (int)(0.5 + size_points / 1.2);
2176             }
2177           else
2178             {
2179               /* Go one smaller than that, if possible */
2180               if (i > 0)
2181                 i--;
2182 
2183               size_points = font_sizes[i];
2184             }
2185         }
2186       else if (strcmp (term->content.str->stryng->str, "larger") == 0)
2187         {
2188           /* Find the standard size equal to or larger than the current size */
2189           int i = 6;
2190 
2191           while (i >= 0 && font_sizes[i] > size_points)
2192             i--;
2193 
2194           if (i < 0) /* original size smaller than any standard size */
2195             i = 0;
2196 
2197           /* Go one larger than that, if possible */
2198           if (i < 6)
2199             i++;
2200 
2201           size_points = font_sizes[i];
2202         }
2203       else
2204         {
2205           return FALSE;
2206         }
2207 
2208       *size = size_points * (resolution / 72.);
2209       return TRUE;
2210 
2211     }
2212   else if (term->type == TERM_NUMBER && term->content.num->type == NUM_PERCENTAGE)
2213     {
2214       *size *= term->content.num->val / 100.;
2215       return TRUE;
2216     }
2217   else if (get_length_from_term (node, term, TRUE, size) == VALUE_FOUND)
2218     {
2219       /* Convert from pixels to Pango units */
2220       *size *= 1024;
2221       return TRUE;
2222     }
2223 
2224   return FALSE;
2225 }
2226 
2227 static gboolean
2228 font_weight_from_term (CRTerm      *term,
2229                        PangoWeight *weight,
2230                        gboolean    *weight_absolute)
2231 {
2232   if (term->type == TERM_NUMBER)
2233     {
2234       int weight_int;
2235 
2236       /* The spec only allows numeric weights from 100-900, though Pango
2237        * will handle any number. We just let anything through.
2238        */
2239       if (term->content.num->type != NUM_GENERIC)
2240         return FALSE;
2241 
2242       weight_int = (int)(0.5 + term->content.num->val);
2243 
2244       *weight = weight_int;
2245       *weight_absolute = TRUE;
2246 
2247     }
2248   else if (term->type == TERM_IDENT)
2249     {
2250       /* FIXME: handle INHERIT */
2251 
2252       if (strcmp (term->content.str->stryng->str, "bold") == 0)
2253         {
2254           *weight = PANGO_WEIGHT_BOLD;
2255           *weight_absolute = TRUE;
2256         }
2257       else if (strcmp (term->content.str->stryng->str, "normal") == 0)
2258         {
2259           *weight = PANGO_WEIGHT_NORMAL;
2260           *weight_absolute = TRUE;
2261         }
2262       else if (strcmp (term->content.str->stryng->str, "bolder") == 0)
2263         {
2264           *weight = PANGO_WEIGHT_BOLD;
2265           *weight_absolute = FALSE;
2266         }
2267       else if (strcmp (term->content.str->stryng->str, "lighter") == 0)
2268         {
2269           *weight = PANGO_WEIGHT_LIGHT;
2270           *weight_absolute = FALSE;
2271         }
2272       else
2273         {
2274           return FALSE;
2275         }
2276 
2277     }
2278   else
2279     {
2280       return FALSE;
2281     }
2282 
2283   return TRUE;
2284 }
2285 
2286 static gboolean
2287 font_style_from_term (CRTerm     *term,
2288                       PangoStyle *style)
2289 {
2290   if (term->type != TERM_IDENT)
2291     return FALSE;
2292 
2293   /* FIXME: handle INHERIT */
2294 
2295   if (strcmp (term->content.str->stryng->str, "normal") == 0)
2296     *style = PANGO_STYLE_NORMAL;
2297   else if (strcmp (term->content.str->stryng->str, "oblique") == 0)
2298     *style = PANGO_STYLE_OBLIQUE;
2299   else if (strcmp (term->content.str->stryng->str, "italic") == 0)
2300     *style = PANGO_STYLE_ITALIC;
2301   else
2302     return FALSE;
2303 
2304   return TRUE;
2305 }
2306 
2307 static gboolean
2308 font_variant_from_term (CRTerm       *term,
2309                         PangoVariant *variant)
2310 {
2311   if (term->type != TERM_IDENT)
2312     return FALSE;
2313 
2314   /* FIXME: handle INHERIT */
2315 
2316   if (strcmp (term->content.str->stryng->str, "normal") == 0)
2317     *variant = PANGO_VARIANT_NORMAL;
2318   else if (strcmp (term->content.str->stryng->str, "small-caps") == 0)
2319     *variant = PANGO_VARIANT_SMALL_CAPS;
2320   else
2321     return FALSE;
2322 
2323   return TRUE;
2324 }
2325 
2326 const PangoFontDescription *
2327 st_theme_node_get_font (StThemeNode *node)
2328 {
2329   /* Initialized despite _set flags to suppress compiler warnings */
2330   PangoStyle font_style = PANGO_STYLE_NORMAL;
2331   gboolean font_style_set = FALSE;
2332   PangoVariant variant = PANGO_VARIANT_NORMAL;
2333   gboolean variant_set = FALSE;
2334   PangoWeight weight = PANGO_WEIGHT_NORMAL;
2335   gboolean weight_absolute = TRUE;
2336   gboolean weight_set = FALSE;
2337   double size = 0.;
2338   gboolean size_set = FALSE;
2339 
2340   char *family = NULL;
2341   double parent_size;
2342   int i;
2343 
2344   if (node->font_desc)
2345     return node->font_desc;
2346 
2347   node->font_desc = pango_font_description_copy (get_parent_font (node));
2348   parent_size = pango_font_description_get_size (node->font_desc);
2349   if (!pango_font_description_get_size_is_absolute (node->font_desc))
2350     {
2351       double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
2352       parent_size *= (resolution / 72.);
2353     }
2354 
2355   ensure_properties (node);
2356 
2357   for (i = 0; i < node->n_properties; i++)
2358     {
2359       CRDeclaration *decl = node->properties[i];
2360 
2361       if (strcmp (decl->property->stryng->str, "font") == 0)
2362         {
2363           PangoStyle tmp_style = PANGO_STYLE_NORMAL;
2364           PangoVariant tmp_variant = PANGO_VARIANT_NORMAL;
2365           PangoWeight tmp_weight = PANGO_WEIGHT_NORMAL;
2366           gboolean tmp_weight_absolute = TRUE;
2367           double tmp_size;
2368           CRTerm *term = decl->value;
2369 
2370           /* A font specification starts with node/variant/weight
2371            * in any order. Each is allowed to be specified only once,
2372            * but we don't enforce that.
2373            */
2374           for (; term; term = term->next)
2375             {
2376               if (font_style_from_term (term, &tmp_style))
2377                 continue;
2378               if (font_variant_from_term (term, &tmp_variant))
2379                 continue;
2380               if (font_weight_from_term (term, &tmp_weight, &tmp_weight_absolute))
2381                 continue;
2382 
2383               break;
2384             }
2385 
2386           /* The size is mandatory */
2387 
2388           if (term == NULL || term->type != TERM_NUMBER)
2389             {
2390               g_warning ("Size missing from font property");
2391               continue;
2392             }
2393 
2394           tmp_size = parent_size;
2395           if (!font_size_from_term (node, term, &tmp_size))
2396             {
2397               g_warning ("Couldn't parse size in font property");
2398               continue;
2399             }
2400 
2401           term = term->next;
2402 
2403           if (term != NULL && term->type && TERM_NUMBER && term->the_operator == DIVIDE)
2404             {
2405               /* Ignore line-height specification */
2406               term = term->next;
2407             }
2408 
2409           /* the font family is mandatory - it is a comma-separated list of
2410            * names.
2411            */
2412           if (!font_family_from_terms (term, &family))
2413             {
2414               g_warning ("Couldn't parse family in font property");
2415               continue;
2416             }
2417 
2418           font_style = tmp_style;
2419           font_style_set = TRUE;
2420           weight = tmp_weight;
2421           weight_absolute = tmp_weight_absolute;
2422           weight_set = TRUE;
2423           variant = tmp_variant;
2424           variant_set = TRUE;
2425 
2426           size = tmp_size;
2427           size_set = TRUE;
2428 
2429         }
2430       else if (strcmp (decl->property->stryng->str, "font-family") == 0)
2431         {
2432           if (!font_family_from_terms (decl->value, &family))
2433             {
2434               g_warning ("Couldn't parse family in font property");
2435               continue;
2436             }
2437         }
2438       else if (strcmp (decl->property->stryng->str, "font-weight") == 0)
2439         {
2440           if (decl->value == NULL || decl->value->next != NULL)
2441             continue;
2442 
2443           if (font_weight_from_term (decl->value, &weight, &weight_absolute))
2444             weight_set = TRUE;
2445         }
2446       else if (strcmp (decl->property->stryng->str, "font-style") == 0)
2447         {
2448           if (decl->value == NULL || decl->value->next != NULL)
2449             continue;
2450 
2451           if (font_style_from_term (decl->value, &font_style))
2452             font_style_set = TRUE;
2453         }
2454       else if (strcmp (decl->property->stryng->str, "font-variant") == 0)
2455         {
2456           if (decl->value == NULL || decl->value->next != NULL)
2457             continue;
2458 
2459           if (font_variant_from_term (decl->value, &variant))
2460             variant_set = TRUE;
2461         }
2462       else if (strcmp (decl->property->stryng->str, "font-size") == 0)
2463         {
2464           gdouble tmp_size;
2465           if (decl->value == NULL || decl->value->next != NULL)
2466             continue;
2467 
2468           tmp_size = parent_size;
2469           if (font_size_from_term (node, decl->value, &tmp_size))
2470             {
2471               size = tmp_size;
2472               size_set = TRUE;
2473             }
2474         }
2475     }
2476 
2477   if (family)
2478     {
2479       pango_font_description_set_family (node->font_desc, family);
2480       g_free (family);
2481     }
2482 
2483   if (size_set)
2484     pango_font_description_set_absolute_size (node->font_desc, size);
2485 
2486   if (weight_set)
2487     {
2488       if (!weight_absolute)
2489         {
2490           /* bolder/lighter are supposed to switch between available styles, but with
2491            * font substitution, that gets to be a pretty fuzzy concept. So we use
2492            * a fixed step of 200. (The spec says 100, but that might not take us from
2493            * normal to bold.
2494            */
2495 
2496           PangoWeight old_weight = pango_font_description_get_weight (node->font_desc);
2497           if (weight == PANGO_WEIGHT_BOLD)
2498             weight = old_weight + 200;
2499           else
2500             weight = old_weight - 200;
2501 
2502           if (weight < 100)
2503             weight = 100;
2504           if (weight > 900)
2505             weight = 900;
2506         }
2507 
2508       pango_font_description_set_weight (node->font_desc, weight);
2509     }
2510 
2511   if (font_style_set)
2512     pango_font_description_set_style (node->font_desc, font_style);
2513   if (variant_set)
2514     pango_font_description_set_variant (node->font_desc, variant);
2515 
2516   return node->font_desc;
2517 }
2518 
2519 /**
2520  * st_theme_node_get_border_image:
2521  * @node: a #StThemeNode
2522  *
2523  * Gets the value for the border-image style property
2524  *
2525  * Return value: (transfer none): the border image, or %NULL
2526  *   if there is no border image.
2527  */
2528 StBorderImage *
2529 st_theme_node_get_border_image (StThemeNode *node)
2530 {
2531   int i;
2532 
2533   if (node->border_image_computed)
2534     return node->border_image;
2535 
2536   node->border_image = NULL;
2537   node->border_image_computed = TRUE;
2538 
2539   ensure_properties (node);
2540 
2541   for (i = node->n_properties - 1; i >= 0; i--)
2542     {
2543       CRDeclaration *decl = node->properties[i];
2544 
2545       if (strcmp (decl->property->stryng->str, "border-image") == 0)
2546         {
2547           CRTerm *term = decl->value;
2548           CRStyleSheet *base_stylesheet;
2549           int borders[4];
2550           int n_borders = 0;
2551           int i;
2552 
2553           const char *url;
2554           int border_top;
2555           int border_right;
2556           int border_bottom;
2557           int border_left;
2558 
2559           char *filename;
2560 
2561           /* Support border-image: none; to suppress a previously specified border image */
2562           if (term_is_none (term))
2563             {
2564               if (term->next == NULL)
2565                 return NULL;
2566               else
2567                 goto next_property;
2568             }
2569 
2570           /* First term must be the URL to the image */
2571           if (term->type != TERM_URI)
2572             goto next_property;
2573 
2574           url = term->content.str->stryng->str;
2575 
2576           term = term->next;
2577 
2578           /* Followed by 0 to 4 numbers or percentages. *Not lengths*. The interpretation
2579            * of a number is supposed to be pixels if the image is pixel based, otherwise CSS pixels.
2580            */
2581           for (i = 0; i < 4; i++)
2582             {
2583               if (term == NULL)
2584                 break;
2585 
2586               if (term->type != TERM_NUMBER)
2587                 goto next_property;
2588 
2589               if (term->content.num->type == NUM_GENERIC)
2590                 {
2591                   borders[n_borders] = (int)(0.5 + term->content.num->val);
2592                   n_borders++;
2593                 }
2594               else if (term->content.num->type == NUM_PERCENTAGE)
2595                 {
2596                   /* This would be easiest to support if we moved image handling into StBorderImage */
2597                   g_warning ("Percentages not supported for border-image");
2598                   goto next_property;
2599                 }
2600               else
2601                 goto next_property;
2602 
2603               term = term->next;
2604             }
2605 
2606           switch (n_borders)
2607             {
2608             case 0:
2609               border_top = border_right = border_bottom = border_left = 0;
2610               break;
2611             case 1:
2612               border_top = border_right = border_bottom = border_left = borders[0];
2613               break;
2614             case 2:
2615               border_top = border_bottom = borders[0];
2616               border_left = border_right = borders[1];
2617               break;
2618             case 3:
2619               border_top = borders[0];
2620               border_left = border_right = borders[1];
2621               border_bottom = borders[2];
2622               break;
2623             case 4:
2624             default:
2625               border_top = borders[0];
2626               border_right = borders[1];
2627               border_bottom = borders[2];
2628               border_left = borders[3];
2629               break;
2630             }
2631 
2632           if (decl->parent_statement != NULL)
2633             base_stylesheet = decl->parent_statement->parent_sheet;
2634           else
2635             base_stylesheet = NULL;
2636 
2637           filename = _st_theme_resolve_url (node->theme, base_stylesheet, url);
2638           if (filename == NULL)
2639             goto next_property;
2640 
2641           node->border_image = st_border_image_new (filename,
2642                                                     border_top, border_right, border_bottom, border_left);
2643 
2644           g_free (filename);
2645 
2646           return node->border_image;
2647         }
2648 
2649     next_property:
2650       ;
2651     }
2652 
2653   return NULL;
2654 }
2655 
2656 /**
2657  * st_theme_node_get_horizontal_padding:
2658  * @node: a #StThemeNode
2659  *
2660  * Gets the total horizonal padding (left + right padding)
2661  *
2662  * Return value: the total horizonal padding
2663  *   in pixels
2664  */
2665 double
2666 st_theme_node_get_horizontal_padding (StThemeNode *node)
2667 {
2668   double padding = 0.0;
2669   padding += st_theme_node_get_padding (node, ST_SIDE_LEFT);
2670   padding += st_theme_node_get_padding (node, ST_SIDE_RIGHT);
2671 
2672   return padding;
2673 }
2674 
2675 /**
2676  * st_theme_node_get_vertical_padding:
2677  * @node: a #StThemeNode
2678  *
2679  * Gets the total vertical padding (top + bottom padding)
2680  *
2681  * Return value: the total vertical padding
2682  *   in pixels
2683  */
2684 double
2685 st_theme_node_get_vertical_padding (StThemeNode *node)
2686 {
2687   double padding = 0.0;
2688   padding += st_theme_node_get_padding (node, ST_SIDE_TOP);
2689   padding += st_theme_node_get_padding (node, ST_SIDE_BOTTOM);
2690 
2691   return padding;
2692 }
2693 
2694 static GetFromTermResult
2695 parse_shadow_property (StThemeNode       *node,
2696                        CRDeclaration     *decl,
2697                        ClutterColor      *color,
2698                        gdouble           *xoffset,
2699                        gdouble           *yoffset,
2700                        gdouble           *blur,
2701                        gdouble           *spread,
2702                        gboolean          *inset)
2703 {
2704   GetFromTermResult result;
2705   CRTerm *term;
2706   int n_offsets = 0;
2707 
2708   /* default values */
2709   color->red = 0x0; color->green = 0x0; color->blue = 0x0; color->alpha = 0xff;
2710   *xoffset = 0.;
2711   *yoffset = 0.;
2712   *blur = 0.;
2713   *spread = 0.;
2714   *inset = FALSE;
2715 
2716   /* The CSS3 draft of the box-shadow property[0] is a lot stricter
2717    * regarding the order of terms:
2718    * If the 'inset' keyword is specified, it has to be first or last,
2719    * and the color may not be mixed with the lengths; while we parse
2720    * length values in the correct order, we allow for arbitrary
2721    * placement of the color and 'inset' keyword.
2722    *
2723    * [0] http://www.w3.org/TR/css3-background/#box-shadow
2724    */
2725   for (term = decl->value; term; term = term->next)
2726     {
2727       if (term->type == TERM_NUMBER)
2728         {
2729           gdouble value;
2730           gdouble multiplier;
2731 
2732           multiplier = (term->unary_op == MINUS_UOP) ? -1. : 1.;
2733           result = get_length_from_term (node, term, FALSE, &value);
2734 
2735           if (result == VALUE_INHERIT)
2736             {
2737               /* we only allow inherit on the line by itself */
2738               if (n_offsets > 0)
2739                 return VALUE_NOT_FOUND;
2740               else
2741                 return VALUE_INHERIT;
2742             }
2743           else if (result == VALUE_FOUND)
2744             {
2745               switch (n_offsets++)
2746                 {
2747                 case 0:
2748                   *xoffset = multiplier * value;
2749                   break;
2750                 case 1:
2751                   *yoffset = multiplier * value;
2752                   break;
2753                 case 2:
2754                   if (multiplier < 0)
2755                       g_warning ("Negative blur values are "
2756                                  "not allowed");
2757                   *blur = value;
2758                   break;
2759                 case 3:
2760                   if (multiplier < 0)
2761                       g_warning ("Negative spread values are "
2762                                  "not allowed");
2763                   *spread = value;
2764                   break;
2765                 }
2766               continue;
2767             }
2768         }
2769       else if (term->type == TERM_IDENT &&
2770                strcmp (term->content.str->stryng->str, "inset") == 0)
2771         {
2772           *inset = TRUE;
2773           continue;
2774         }
2775 
2776       result = get_color_from_term (node, term, color);
2777 
2778       if (result == VALUE_INHERIT)
2779         {
2780           if (n_offsets > 0)
2781             return VALUE_NOT_FOUND;
2782           else
2783             return VALUE_INHERIT;
2784         }
2785       else if (result == VALUE_FOUND)
2786         {
2787           continue;
2788         }
2789     }
2790 
2791   /* The only required terms are the x and y offsets
2792    */
2793   if (n_offsets >= 2)
2794     return VALUE_FOUND;
2795   else
2796     return VALUE_NOT_FOUND;
2797 }
2798 
2799 /**
2800  * st_theme_node_lookup_shadow:
2801  * @node: a #StThemeNode
2802  * @property_name: The name of the shadow property
2803  * @inherit: if %TRUE, if a value is not found for the property on the
2804  *   node, then it will be looked up on the parent node, and then on the
2805  *   parent's parent, and so forth. Note that if the property has a
2806  *   value of 'inherit' it will be inherited even if %FALSE is passed
2807  *   in for @inherit; this only affects the default behavior for inheritance.
2808  * @shadow: (out): location to store the shadow
2809  *
2810  * If the property is not found, the value in the shadow variable will not
2811  * be changed.
2812  *
2813  * Generically looks up a property containing a set of shadow values. When
2814  * specific getters (like st_theme_node_get_box_shadow ()) exist, they
2815  * should be used instead. They are cached, so more efficient, and have
2816  * handling for shortcut properties and other details of CSS.
2817  *
2818  * See also st_theme_node_get_shadow(), which provides a simpler API.
2819  *
2820  * Return value: %TRUE if the property was found in the properties for this
2821  *  theme node (or in the properties of parent nodes when inheriting.)
2822  */
2823 gboolean
2824 st_theme_node_lookup_shadow (StThemeNode  *node,
2825                              const char   *property_name,
2826                              gboolean      inherit,
2827                              StShadow    **shadow)
2828 {
2829   ClutterColor color = { 0., };
2830   gdouble xoffset = 0.;
2831   gdouble yoffset = 0.;
2832   gdouble blur = 0.;
2833   gdouble spread = 0.;
2834   gboolean inset = FALSE;
2835 
2836   int i;
2837 
2838   ensure_properties (node);
2839 
2840   for (i = node->n_properties - 1; i >= 0; i--)
2841     {
2842       CRDeclaration *decl = node->properties[i];
2843 
2844       if (strcmp (decl->property->stryng->str, property_name) == 0)
2845         {
2846           GetFromTermResult result = parse_shadow_property (node,
2847                                                             decl,
2848                                                             &color,
2849                                                             &xoffset,
2850                                                             &yoffset,
2851                                                             &blur,
2852                                                             &spread,
2853                                                             &inset);
2854           if (result == VALUE_FOUND)
2855             {
2856               *shadow = st_shadow_new (&color,
2857                                        xoffset, yoffset,
2858                                        blur, spread,
2859                                        inset);
2860               return TRUE;
2861             }
2862           else if (result == VALUE_INHERIT)
2863             {
2864               if (node->parent_node)
2865                 return st_theme_node_lookup_shadow (node->parent_node,
2866                                                     property_name,
2867                                                     inherit,
2868                                                     shadow);
2869               else
2870                 break;
2871             }
2872         }
2873     }
2874 
2875     if (inherit && node->parent_node)
2876       return st_theme_node_lookup_shadow (node->parent_node,
2877                                           property_name,
2878                                           inherit,
2879                                           shadow);
2880 
2881   return FALSE;
2882 }
2883 
2884 /**
2885  * st_theme_node_get_shadow:
2886  * @node: a #StThemeNode
2887  * @property_name: The name of the shadow property
2888  *
2889  * Generically looks up a property containing a set of shadow values. When
2890  * specific getters (like st_theme_node_get_box_shadow()) exist, they
2891  * should be used instead. They are cached, so more efficient, and have
2892  * handling for shortcut properties and other details of CSS.
2893  *
2894  * Like st_theme_get_length(), this does not print a warning if the property is
2895  * not found; it just returns %NULL
2896  *
2897  * See also st_theme_node_lookup_shadow (), which provides more options.
2898  *
2899  * Return value: (transfer full): the shadow, or %NULL if the property was not found.
2900  */
2901 StShadow *
2902 st_theme_node_get_shadow (StThemeNode  *node,
2903                           const char   *property_name)
2904 {
2905   StShadow *shadow;
2906 
2907   if (st_theme_node_lookup_shadow (node, property_name, FALSE, &shadow))
2908     return shadow;
2909   else
2910     return NULL;
2911 }
2912 
2913 /**
2914  * st_theme_node_get_box_shadow:
2915  * @node: a #StThemeNode
2916  *
2917  * Gets the value for the box-shadow style property
2918  *
2919  * Return value: (transfer none): the node's shadow, or %NULL
2920  *   if node has no shadow
2921  */
2922 StShadow *
2923 st_theme_node_get_box_shadow (StThemeNode *node)
2924 {
2925   StShadow *shadow;
2926 
2927   if (node->box_shadow_computed)
2928     return node->box_shadow;
2929 
2930   node->box_shadow = NULL;
2931   node->box_shadow_computed = TRUE;
2932 
2933   if (st_theme_node_lookup_shadow (node,
2934                                    "box-shadow",
2935                                    FALSE,
2936                                    &shadow))
2937     {
2938       node->box_shadow = shadow;
2939 
2940       return node->box_shadow;
2941     }
2942 
2943   return NULL;
2944 }
2945 
2946 /**
2947  * st_theme_node_get_background_image_shadow:
2948  * @node: a #StThemeNode
2949  *
2950  * Gets the value for the -st-background-image-shadow style property
2951  *
2952  * Return value: (transfer none): the node's background image shadow, or %NULL
2953  *   if node has no such shadow
2954  */
2955 StShadow *
2956 st_theme_node_get_background_image_shadow (StThemeNode *node)
2957 {
2958   StShadow *shadow;
2959 
2960   if (node->background_image_shadow_computed)
2961     return node->background_image_shadow;
2962 
2963   node->background_image_shadow = NULL;
2964   node->background_image_shadow_computed = TRUE;
2965 
2966   if (st_theme_node_lookup_shadow (node,
2967                                    "-st-background-image-shadow",
2968                                    FALSE,
2969                                    &shadow))
2970     {
2971       if (shadow->inset)
2972         {
2973           g_warning ("The -st-background-image-shadow property does not "
2974                      "support inset shadows");
2975           st_shadow_unref (shadow);
2976           shadow = NULL;
2977         }
2978 
2979       node->background_image_shadow = shadow;
2980 
2981       return node->background_image_shadow;
2982     }
2983 
2984   return NULL;
2985 }
2986 
2987 /**
2988  * st_theme_node_get_text_shadow:
2989  * @node: a #StThemeNode
2990  *
2991  * Gets the value for the text-shadow style property
2992  *
2993  * Return value: (transfer none): the node's text-shadow, or %NULL
2994  *   if node has no text-shadow
2995  */
2996 StShadow *
2997 st_theme_node_get_text_shadow (StThemeNode *node)
2998 {
2999   StShadow *result = NULL;
3000 
3001   if (node->text_shadow_computed)
3002     return node->text_shadow;
3003 
3004   ensure_properties (node);
3005 
3006   if (!st_theme_node_lookup_shadow (node,
3007                                     "text-shadow",
3008                                     FALSE,
3009                                     &result))
3010     {
3011       if (node->parent_node)
3012         {
3013           result = st_theme_node_get_text_shadow (node->parent_node);
3014           if (result)
3015             st_shadow_ref (result);
3016         }
3017     }
3018 
3019   if (result && result->inset)
3020     {
3021       g_warning ("The text-shadow property does not support inset shadows");
3022       st_shadow_unref (result);
3023       result = NULL;
3024     }
3025 
3026   node->text_shadow = result;
3027   node->text_shadow_computed = TRUE;
3028 
3029   return result;
3030 }
3031 
3032 /**
3033  * st_theme_node_get_icon_colors:
3034  * @node: a #StThemeNode
3035  *
3036  * Gets the colors that should be used for colorizing symbolic icons according
3037  * the style of this node.
3038  *
3039  * Return value: (transfer none): the icon colors to use for this theme node
3040  */
3041 StIconColors *
3042 st_theme_node_get_icon_colors (StThemeNode *node)
3043 {
3044   /* Foreground here will always be the same as st_theme_node_get_foreground_color(),
3045    * but there's a loss of symmetry and little efficiency win if we try to exploit
3046    * that. */
3047 
3048   enum {
3049     FOREGROUND = 1 << 0,
3050     WARNING = 1 << 1,
3051     ERROR = 1 << 2,
3052     SUCCESS = 1 << 3
3053   };
3054 
3055   gboolean shared_with_parent;
3056   int i;
3057   ClutterColor color = { 0, };
3058 
3059   guint still_need = FOREGROUND | WARNING | ERROR | SUCCESS;
3060 
3061   g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
3062 
3063   if (node->icon_colors)
3064     return node->icon_colors;
3065 
3066   if (node->parent_node)
3067     {
3068       node->icon_colors = st_theme_node_get_icon_colors (node->parent_node);
3069       shared_with_parent = TRUE;
3070     }
3071   else
3072     {
3073       node->icon_colors = st_icon_colors_new ();
3074       node->icon_colors->foreground = BLACK_COLOR;
3075       node->icon_colors->warning = DEFAULT_WARNING_COLOR;
3076       node->icon_colors->error = DEFAULT_ERROR_COLOR;
3077       node->icon_colors->success = DEFAULT_SUCCESS_COLOR;
3078       shared_with_parent = FALSE;
3079     }
3080 
3081   ensure_properties (node);
3082 
3083   for (i = node->n_properties - 1; i >= 0 && still_need != 0; i--)
3084     {
3085       CRDeclaration *decl = node->properties[i];
3086       GetFromTermResult result = VALUE_NOT_FOUND;
3087       guint found = 0;
3088 
3089       if ((still_need & FOREGROUND) != 0 &&
3090           strcmp (decl->property->stryng->str, "color") == 0)
3091         {
3092           found = FOREGROUND;
3093           result = get_color_from_term (node, decl->value, &color);
3094         }
3095       else if ((still_need & WARNING) != 0 &&
3096                strcmp (decl->property->stryng->str, "warning-color") == 0)
3097         {
3098           found = WARNING;
3099           result = get_color_from_term (node, decl->value, &color);
3100         }
3101       else if ((still_need & ERROR) != 0 &&
3102                strcmp (decl->property->stryng->str, "error-color") == 0)
3103         {
3104           found = ERROR;
3105           result = get_color_from_term (node, decl->value, &color);
3106         }
3107       else if ((still_need & SUCCESS) != 0 &&
3108                strcmp (decl->property->stryng->str, "success-color") == 0)
3109         {
3110           found = SUCCESS;
3111           result = get_color_from_term (node, decl->value, &color);
3112         }
3113 
3114       if (result == VALUE_INHERIT)
3115         {
3116           still_need &= ~found;
3117         }
3118       else if (result == VALUE_FOUND)
3119         {
3120           still_need &= ~found;
3121           if (shared_with_parent)
3122             {
3123               node->icon_colors = st_icon_colors_copy (node->icon_colors);
3124               shared_with_parent = FALSE;
3125             }
3126 
3127           switch (found)
3128             {
3129             case FOREGROUND:
3130               node->icon_colors->foreground = color;
3131               break;
3132             case WARNING:
3133               node->icon_colors->warning = color;
3134               break;
3135             case ERROR:
3136               node->icon_colors->error = color;
3137               break;
3138             case SUCCESS:
3139               node->icon_colors->success = color;
3140               break;
3141             }
3142         }
3143     }
3144 
3145   if (shared_with_parent)
3146     st_icon_colors_ref (node->icon_colors);
3147 
3148   return node->icon_colors;
3149 }
3150 
3151 static float
3152 get_width_inc (StThemeNode *node)
3153 {
3154   return ((int)(0.5 + node->border_width[ST_SIDE_LEFT]) + node->padding[ST_SIDE_LEFT] +
3155           (int)(0.5 + node->border_width[ST_SIDE_RIGHT]) + node->padding[ST_SIDE_RIGHT]);
3156 }
3157 
3158 static float
3159 get_height_inc (StThemeNode *node)
3160 {
3161   return ((int)(0.5 + node->border_width[ST_SIDE_TOP]) + node->padding[ST_SIDE_TOP] +
3162           (int)(0.5 + node->border_width[ST_SIDE_BOTTOM]) + node->padding[ST_SIDE_BOTTOM]);
3163 }
3164 
3165 /**
3166  * st_theme_node_adjust_for_height:
3167  * @node: a #StThemeNode
3168  * @for_height: (inout): the "for height" to adjust
3169  *
3170  * Adjusts a "for height" passed to clutter_actor_get_preferred_width() to
3171  * account for borders and padding. This is a convenience function meant
3172  * to be called from a get_preferred_width() method of a #ClutterActor
3173  * subclass. The value after adjustment is the height available for the actor's
3174  * content.
3175  */
3176 void
3177 st_theme_node_adjust_for_height (StThemeNode  *node,
3178                                  float        *for_height)
3179 {
3180   g_return_if_fail (ST_IS_THEME_NODE (node));
3181   g_return_if_fail (for_height != NULL);
3182 
3183   if (*for_height >= 0)
3184     {
3185       float height_inc = get_height_inc (node);
3186       *for_height = MAX (0, *for_height - height_inc);
3187     }
3188 }
3189 
3190 /**
3191  * st_theme_node_adjust_preferred_width:
3192  * @node: a #StThemeNode
3193  * @min_width_p: (inout) (allow-none): the minimum width to adjust
3194  * @natural_width_p: (inout): the natural width to adjust
3195  *
3196  * Adjusts the minimum and natural width computed for an actor by
3197  * adding on the necessary space for borders and padding and taking
3198  * into account any minimum or maximum width. This is a convenience
3199  * function meant to be called from the get_preferred_width() method
3200  * of a #ClutterActor subclass
3201  */
3202 void
3203 st_theme_node_adjust_preferred_width (StThemeNode  *node,
3204                                       float        *min_width_p,
3205                                       float        *natural_width_p)
3206 {
3207   float width_inc;
3208 
3209   g_return_if_fail (ST_IS_THEME_NODE (node));
3210 
3211   _st_theme_node_ensure_geometry (node);
3212 
3213   width_inc = get_width_inc (node);
3214 
3215   if (min_width_p)
3216     {
3217       if (node->min_width != -1)
3218         *min_width_p = node->min_width;
3219       *min_width_p += width_inc;
3220     }
3221 
3222   if (natural_width_p)
3223     {
3224       if (node->width != -1)
3225         *natural_width_p = node->width;
3226       if (node->max_width != -1)
3227         *natural_width_p = MIN (*natural_width_p, node->max_width);
3228       *natural_width_p += width_inc;
3229     }
3230 }
3231 
3232 /**
3233  * st_theme_node_adjust_for_width:
3234  * @node: a #StThemeNode
3235  * @for_width: (inout): the "for width" to adjust
3236  *
3237  * Adjusts a "for width" passed to clutter_actor_get_preferred_height() to
3238  * account for borders and padding. This is a convenience function meant
3239  * to be called from a get_preferred_height() method of a #ClutterActor
3240  * subclass. The value after adjustment is the width available for the actor's
3241  * content.
3242  */
3243 void
3244 st_theme_node_adjust_for_width (StThemeNode  *node,
3245                                 float        *for_width)
3246 {
3247   g_return_if_fail (ST_IS_THEME_NODE (node));
3248   g_return_if_fail (for_width != NULL);
3249 
3250   if (*for_width >= 0)
3251     {
3252       float width_inc = get_width_inc (node);
3253       *for_width = MAX (0, *for_width - width_inc);
3254     }
3255 }
3256 
3257 /**
3258  * st_theme_node_adjust_preferred_height:
3259  * @node: a #StThemeNode
3260  * @min_height_p: (inout) (allow-none): the minimum height to adjust
3261  * @natural_height_p: (inout): the natural height to adjust
3262  *
3263  * Adjusts the minimum and natural height computed for an actor by
3264  * adding on the necessary space for borders and padding and taking
3265  * into account any minimum or maximum height. This is a convenience
3266  * function meant to be called from the get_preferred_height() method
3267  * of a #ClutterActor subclass
3268  */
3269 void
3270 st_theme_node_adjust_preferred_height (StThemeNode  *node,
3271                                        float           *min_height_p,
3272                                        float           *natural_height_p)
3273 {
3274   float height_inc;
3275 
3276   g_return_if_fail (ST_IS_THEME_NODE (node));
3277 
3278   _st_theme_node_ensure_geometry (node);
3279 
3280   height_inc = get_height_inc (node);
3281 
3282   if (min_height_p)
3283     {
3284       if (node->min_height != -1)
3285         *min_height_p = node->min_height;
3286       *min_height_p += height_inc;
3287     }
3288   if (natural_height_p)
3289     {
3290       if (node->height != -1)
3291         *natural_height_p = node->height;
3292       if (node->max_height != -1)
3293         *natural_height_p = MIN (*natural_height_p, node->max_height);
3294       *natural_height_p += height_inc;
3295     }
3296 }
3297 
3298 /**
3299  * st_theme_node_get_content_box:
3300  * @node: a #StThemeNode
3301  * @allocation: the box allocated to a #ClutterAlctor
3302  * @content_box: (out caller-allocates): computed box occupied by the actor's content
3303  *
3304  * Gets the box within an actor's allocation that contents the content
3305  * of an actor (excluding borders and padding). This is a convenience function
3306  * meant to be used from the allocate() or paint() methods of a #ClutterActor
3307  * subclass.
3308  */
3309 void
3310 st_theme_node_get_content_box (StThemeNode           *node,
3311                                const ClutterActorBox *allocation,
3312                                ClutterActorBox       *content_box)
3313 {
3314   double noncontent_left, noncontent_top, noncontent_right, noncontent_bottom;
3315   double avail_width, avail_height, content_width, content_height;
3316 
3317   g_return_if_fail (ST_IS_THEME_NODE (node));
3318 
3319   _st_theme_node_ensure_geometry (node);
3320 
3321   avail_width = allocation->x2 - allocation->x1;
3322   avail_height = allocation->y2 - allocation->y1;
3323 
3324   noncontent_left = node->border_width[ST_SIDE_LEFT] + node->padding[ST_SIDE_LEFT];
3325   noncontent_top = node->border_width[ST_SIDE_TOP] + node->padding[ST_SIDE_TOP];
3326   noncontent_right = node->border_width[ST_SIDE_RIGHT] + node->padding[ST_SIDE_RIGHT];
3327   noncontent_bottom = node->border_width[ST_SIDE_BOTTOM] + node->padding[ST_SIDE_BOTTOM];
3328 
3329   content_box->x1 = (int)(0.5 + noncontent_left);
3330   content_box->y1 = (int)(0.5 + noncontent_top);
3331 
3332   content_width = avail_width - noncontent_left - noncontent_right;
3333   if (content_width < 0)
3334     content_width = 0;
3335   content_height = avail_height - noncontent_top - noncontent_bottom;
3336   if (content_height < 0)
3337     content_height = 0;
3338 
3339   content_box->x2 = (int)(0.5 + content_box->x1 + content_width);
3340   content_box->y2 = (int)(0.5 + content_box->y1 + content_height);
3341 }
3342 
3343 /**
3344  * st_theme_node_get_background_paint_box:
3345  * @node: a #StThemeNode
3346  * @allocation: the box allocated to a #ClutterActor
3347  * @paint_box: (out caller-allocates): computed box occupied when painting the actor's background
3348  *
3349  * Gets the box used to paint the actor's background, including the area
3350  * occupied by properties which paint outside the actor's assigned allocation.
3351  */
3352 void
3353 st_theme_node_get_background_paint_box (StThemeNode           *node,
3354                                         const ClutterActorBox *actor_box,
3355                                         ClutterActorBox       *paint_box)
3356 {
3357   StShadow *background_image_shadow;
3358   ClutterActorBox shadow_box;
3359 
3360   g_return_if_fail (ST_IS_THEME_NODE (node));
3361   g_return_if_fail (actor_box != NULL);
3362   g_return_if_fail (paint_box != NULL);
3363 
3364   background_image_shadow = st_theme_node_get_background_image_shadow (node);
3365 
3366   *paint_box = *actor_box;
3367 
3368   if (!background_image_shadow)
3369     return;
3370 
3371   st_shadow_get_box (background_image_shadow, actor_box, &shadow_box);
3372 
3373   paint_box->x1 = MIN (paint_box->x1, shadow_box.x1);
3374   paint_box->x2 = MAX (paint_box->x2, shadow_box.x2);
3375   paint_box->y1 = MIN (paint_box->y1, shadow_box.y1);
3376   paint_box->y2 = MAX (paint_box->y2, shadow_box.y2);
3377 }
3378 
3379 /**
3380  * st_theme_node_get_paint_box:
3381  * @node: a #StThemeNode
3382  * @allocation: the box allocated to a #ClutterActor
3383  * @paint_box: (out caller-allocates): computed box occupied when painting the actor
3384  *
3385  * Gets the box used to paint the actor, including the area occupied
3386  * by properties which paint outside the actor's assigned allocation.
3387  * When painting @node to an offscreen buffer, this function can be
3388  * used to determine the necessary size of the buffer.
3389  */
3390 void
3391 st_theme_node_get_paint_box (StThemeNode           *node,
3392                              const ClutterActorBox *actor_box,
3393                              ClutterActorBox       *paint_box)
3394 {
3395   StShadow *box_shadow;
3396   ClutterActorBox shadow_box;
3397   int outline_width;
3398 
3399   g_return_if_fail (ST_IS_THEME_NODE (node));
3400   g_return_if_fail (actor_box != NULL);
3401   g_return_if_fail (paint_box != NULL);
3402 
3403   box_shadow = st_theme_node_get_box_shadow (node);
3404   outline_width = st_theme_node_get_outline_width (node);
3405 
3406   st_theme_node_get_background_paint_box (node, actor_box, paint_box);
3407 
3408   if (!box_shadow && !outline_width)
3409     return;
3410 
3411   paint_box->x1 -= outline_width;
3412   paint_box->x2 += outline_width;
3413   paint_box->y1 -= outline_width;
3414   paint_box->y2 += outline_width;
3415 
3416   if (box_shadow)
3417     {
3418       st_shadow_get_box (box_shadow, actor_box, &shadow_box);
3419 
3420       paint_box->x1 = MIN (paint_box->x1, shadow_box.x1);
3421       paint_box->x2 = MAX (paint_box->x2, shadow_box.x2);
3422       paint_box->y1 = MIN (paint_box->y1, shadow_box.y1);
3423       paint_box->y2 = MAX (paint_box->y2, shadow_box.y2);
3424     }
3425 }
3426 
3427 /**
3428  * st_theme_node_geometry_equal:
3429  * @node: a #StThemeNode
3430  * @other: a different #StThemeNode
3431  *
3432  * Tests if two theme nodes have the same borders and padding; this can be
3433  * used to optimize having to relayout when the style applied to a Clutter
3434  * actor changes colors without changing the geometry.
3435  */
3436 gboolean
3437 st_theme_node_geometry_equal (StThemeNode *node,
3438                               StThemeNode *other)
3439 {
3440   StSide side;
3441 
3442   g_return_val_if_fail (ST_IS_THEME_NODE (node), FALSE);
3443   g_return_val_if_fail (ST_IS_THEME_NODE (other), FALSE);
3444 
3445   _st_theme_node_ensure_geometry (node);
3446   _st_theme_node_ensure_geometry (other);
3447 
3448   for (side = ST_SIDE_TOP; side <= ST_SIDE_LEFT; side++)
3449     {
3450       if (node->border_width[side] != other->border_width[side])
3451         return FALSE;
3452       if (node->padding[side] != other->padding[side])
3453         return FALSE;
3454     }
3455 
3456   if (node->width != other->width || node->height != other->height)
3457     return FALSE;
3458   if (node->min_width != other->min_width || node->min_height != other->min_height)
3459     return FALSE;
3460   if (node->max_width != other->max_width || node->max_height != other->max_height)
3461     return FALSE;
3462 
3463   return TRUE;
3464 }
3465 
3466 /**
3467  * st_theme_node_paint_equal:
3468  * @node: a #StThemeNode
3469  * @other: a different #StThemeNode
3470  *
3471  * Check if st_theme_node_paint() will paint identically for @node as it does
3472  * for @other. Note that in some cases this function may return %TRUE even
3473  * if there is no visible difference in the painting.
3474  *
3475  * Return value: %TRUE if the two theme nodes paint identically. %FALSE if the
3476  *   two nodes potentially paint differently.
3477  */
3478 gboolean
3479 st_theme_node_paint_equal (StThemeNode *node,
3480                            StThemeNode *other)
3481 {
3482   StBorderImage *border_image, *other_border_image;
3483   StShadow *shadow, *other_shadow;
3484   int i;
3485 
3486   g_return_val_if_fail (ST_IS_THEME_NODE (node), FALSE);
3487   g_return_val_if_fail (ST_IS_THEME_NODE (other), FALSE);
3488 
3489   _st_theme_node_ensure_background (node);
3490   _st_theme_node_ensure_background (other);
3491 
3492   if (!clutter_color_equal (&node->background_color, &other->background_color))
3493     return FALSE;
3494 
3495   if (node->background_gradient_type != other->background_gradient_type)
3496     return FALSE;
3497 
3498   if (node->background_gradient_type != ST_GRADIENT_NONE &&
3499       !clutter_color_equal (&node->background_gradient_end, &other->background_gradient_end))
3500     return FALSE;
3501 
3502   if (g_strcmp0 (node->background_image, other->background_image) != 0)
3503     return FALSE;
3504 
3505   _st_theme_node_ensure_geometry (node);
3506   _st_theme_node_ensure_geometry (other);
3507 
3508   for (i = 0; i < 4; i++)
3509     {
3510       if (node->border_width[i] != other->border_width[i])
3511         return FALSE;
3512 
3513       if (node->border_width[i] > 0 &&
3514           !clutter_color_equal (&node->border_color[i], &other->border_color[i]))
3515         return FALSE;
3516 
3517       if (node->border_radius[i] != other->border_radius[i])
3518         return FALSE;
3519     }
3520 
3521   if (node->outline_width != other->outline_width)
3522     return FALSE;
3523 
3524   if (node->outline_width > 0 &&
3525       !clutter_color_equal (&node->outline_color, &other->outline_color))
3526     return FALSE;
3527 
3528   border_image = st_theme_node_get_border_image (node);
3529   other_border_image = st_theme_node_get_border_image (other);
3530 
3531   if ((border_image == NULL) != (other_border_image == NULL))
3532     return FALSE;
3533 
3534   if (border_image != NULL && !st_border_image_equal (border_image, other_border_image))
3535     return FALSE;
3536 
3537   shadow = st_theme_node_get_box_shadow (node);
3538   other_shadow = st_theme_node_get_box_shadow (other);
3539 
3540   if ((shadow == NULL) != (other_shadow == NULL))
3541     return FALSE;
3542 
3543   if (shadow != NULL && !st_shadow_equal (shadow, other_shadow))
3544     return FALSE;
3545 
3546   shadow = st_theme_node_get_background_image_shadow (node);
3547   other_shadow = st_theme_node_get_background_image_shadow (other);
3548 
3549   if ((shadow == NULL) != (other_shadow == NULL))
3550     return FALSE;
3551 
3552   if (shadow != NULL && !st_shadow_equal (shadow, other_shadow))
3553     return FALSE;
3554 
3555   return TRUE;
3556 }