[gnome-shell/shell-toolkit: 14/32] Import stylesheet code from hippo-canvas



commit 2412a89445d52cdd319040f8b6d9cf3543fc7f20
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Sat Sep 19 20:43:49 2009 -0400

    Import stylesheet code from hippo-canvas
    
    Import:
    
      HippoCanvasTheme      => StTheme
      HippoCanvasThemeImage => StThemeImage
      HippoCanvasStyle      => StThemeNode
    
    StThemeContext is a new class managing the theme for a stage and
    global properties like resolution.
    
    test-theme.c is a newly written test program to do verification of the
    style matching and property handling rules.
    
    Various changes are made in the import:
    
     - Comprehensive reindentation
     - guint32 pixels replaced with ClutterColor
     - General pseudo-class support added
     - Old-fashioned (non-bordered) background image support added, though
       with no support for repeat, etc.
     - Bug fixes for problems revealed by test program
    
    https://bugzilla.gnome.org/show_bug.cgi?id=595990

 .gitignore                |    1 +
 configure.ac              |    1 +
 src/Makefile-st.am        |   21 +-
 src/Makefile.am           |    5 +-
 src/st/st-theme-context.c |  218 ++++++
 src/st/st-theme-context.h |   40 +
 src/st/st-theme-image.c   |   92 +++
 src/st/st-theme-image.h   |   38 +
 src/st/st-theme-node.c    | 1777 +++++++++++++++++++++++++++++++++++++++++++++
 src/st/st-theme-node.h    |  115 +++
 src/st/st-theme-private.h |   22 +
 src/st/st-theme.c         | 1032 ++++++++++++++++++++++++++
 src/st/st-theme.h         |   28 +
 src/st/test-theme.c       |  292 ++++++++
 src/st/test-theme.css     |   68 ++
 15 files changed, 3747 insertions(+), 3 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index ba962a7..d39cc63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,7 @@ src/gnomeshell-taskpanel
 src/gnome-shell
 src/test-recorder
 src/test-recorder.ogg
+src/test-theme
 stamp-h1
 tests/run-test.sh
 xmldocs.make
diff --git a/configure.ac b/configure.ac
index b1fd289..9a0452f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -60,6 +60,7 @@ PKG_CHECK_MODULES(TIDY, clutter-1.0)
 PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-2.0 libccss-1 >= 0.3.1 clutter-imcontext-0.1)
 PKG_CHECK_MODULES(BIG, clutter-1.0 gtk+-2.0 librsvg-2.0)
 PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0)
+PKG_CHECK_MODULES(TOOLKIT, clutter-1.0 libcroco-0.6)
 PKG_CHECK_MODULES(TRAY, gtk+-2.0)
 
 MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin
diff --git a/src/Makefile-st.am b/src/Makefile-st.am
index 2967054..454dce4 100644
--- a/src/Makefile-st.am
+++ b/src/Makefile-st.am
@@ -73,7 +73,6 @@ st_source_h =					\
     st/st-clipboard.h				\
     st/st-entry.h				\
     st/st-label.h				\
-    st/st-private.h				\
     st/st-stylable.h				\
     st/st-style.h				\
     st/st-scrollable.h				\
@@ -82,11 +81,19 @@ st_source_h =					\
     st/st-subtexture.h				\
     st/st-texture-cache.h			\
     st/st-texture-frame.h			\
+    st/st-theme.h				\
+    st/st-theme-context.h			\
+    st/st-theme-image.h				\
+    st/st-theme-node.h				\
     st/st-tooltip.h				\
     st/st-types.h				\
     st/st-widget.h				\
     $(NULL)
 
+st_source_private_h =				\
+    st/st-private.h				\
+    st/st-theme-private.h
+
 # please, keep this sorted alphabetically
 st_source_c =					\
     st/st-adjustment.c				\
@@ -106,6 +113,10 @@ st_source_c =					\
     st/st-subtexture.c				\
     st/st-texture-cache.c			\
     st/st-texture-frame.c			\
+    st/st-theme.c				\
+    st/st-theme-context.c			\
+    st/st-theme-image.c				\
+    st/st-theme-node.c				\
     st/st-tooltip.c				\
     st/st-widget.c				\
     $(NULL)
@@ -115,8 +126,16 @@ noinst_LTLIBRARIES += libst-1.0.la
 libst_1_0_la_LIBADD = $(ST_LIBS)
 libst_1_0_la_SOURCES =				\
     $(st_source_c)				\
+    $(st_source_private_c)			\
     $(st_source_h)				\
     $(st_built_sources)				\
     $(NULL)
 libst_1_0_la_CPPFLAGS = $(st_cflags)
 libst_1_0_la_LDFLAGS = $(LDADD)
+
+noinst_PROGRAMS += test-theme
+
+test_theme_CPPFLAGS = $(st_cflags)
+test_theme_LDADD = libst-1.0.la
+
+test_theme_SOURCES = st/test-theme.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 04255cb..b747cf4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,6 +4,7 @@ CLEANFILES =
 EXTRA_DIST =
 libexec_PROGRAMS =
 noinst_LTLIBRARIES =
+noinst_PROGRAMS =
 
 .AUTOPARALLEL:
 
@@ -95,7 +96,7 @@ libgnome_shell_la_SOURCES =			\
 	shell-wm.c				\
 	shell-wm.h
 
-non_gir_sources =						\
+non_gir_sources =				\
 	shell-embedded-window-private.h
 
 shell_recorder_sources =        \
@@ -113,7 +114,7 @@ if BUILD_RECORDER
 libgnome_shell_la_SOURCES += $(shell_recorder_sources)
 non_gir_sources += $(shell_recorder_non_gir_sources)
 
-noinst_PROGRAMS = test-recorder
+noinst_PROGRAMS += test-recorder
 
 test_recorder_CPPFLAGS = $(TEST_SHELL_RECORDER_CFLAGS)
 test_recorder_LDADD = $(TEST_SHELL_RECORDER_LIBS)
diff --git a/src/st/st-theme-context.c b/src/st/st-theme-context.c
new file mode 100644
index 0000000..e26d582
--- /dev/null
+++ b/src/st/st-theme-context.c
@@ -0,0 +1,218 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <config.h>
+
+#include "st-theme.h"
+#include "st-theme-context.h"
+
+struct _StThemeContext {
+  GObject parent;
+
+  double resolution;
+  PangoFontDescription *font;
+  StThemeNode *root_node;
+  StTheme *theme;
+};
+
+struct _StThemeContextClass {
+  GObjectClass parent_class;
+};
+
+enum
+{
+  CHANGED,
+
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (StThemeContext, st_theme_context, G_TYPE_OBJECT)
+
+static void
+st_theme_context_finalize (GObject *object)
+{
+  StThemeContext *context = ST_THEME_CONTEXT (object);
+
+  if (context->root_node)
+    g_object_unref (context->root_node);
+  if (context->theme)
+    g_object_unref (context->theme);
+
+  pango_font_description_free (context->font);
+
+  G_OBJECT_CLASS (st_theme_context_parent_class)->finalize (object);
+}
+
+static void
+st_theme_context_class_init (StThemeContextClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = st_theme_context_finalize;
+
+  signals[CHANGED] =
+    g_signal_new ("changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, /* no default handler slot */
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+}
+
+static void
+st_theme_context_init (StThemeContext *context)
+{
+  context->resolution = 96.;
+  context->font = pango_font_description_from_string ("sans-serif 10");
+}
+
+StThemeContext *
+st_theme_context_new (void)
+{
+  StThemeContext *context;
+
+  context = g_object_new (ST_TYPE_THEME_CONTEXT, NULL);
+
+  return context;
+}
+
+static void
+on_stage_destroy (ClutterStage *stage)
+{
+  StThemeContext *context = st_theme_context_get_for_stage (stage);
+
+  g_object_set_data (G_OBJECT (stage), "st-theme-context", NULL);
+  g_object_unref (context);
+}
+
+/**
+ * st_theme_context_get_for_stage:
+ * @stage: a #ClutterStage
+ *
+ * Gets a singleton theme context associated with the stage.
+ *
+ * Return value: (transfer none): the singleton theme context for the stage
+ */
+StThemeContext *
+st_theme_context_get_for_stage (ClutterStage *stage)
+{
+  StThemeContext *context;
+
+  g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
+
+  context = g_object_get_data (G_OBJECT (stage), "st-theme-context");
+  if (context)
+    return context;
+
+  context = st_theme_context_new ();
+  g_object_set_data (G_OBJECT (stage), "st-theme-context", context);
+  g_signal_connect (stage, "destroy",
+                    G_CALLBACK (on_stage_destroy), NULL);
+
+  return context;
+}
+
+/**
+ * st_theme_context_set_theme:
+ * @context: a #StThemeContext
+ *
+ * Sets the default set of theme stylesheets for the context. This theme will
+ * be used for the root node and for nodes descending from it, unless some other
+ * style is explicitely specified.
+ */
+void
+st_theme_context_set_theme (StThemeContext          *context,
+                            StTheme                 *theme)
+{
+  g_return_if_fail (ST_IS_THEME_CONTEXT (context));
+  g_return_if_fail (theme == NULL || ST_IS_THEME (theme));
+
+  if (context->theme != theme)
+    {
+      if (context->theme)
+        g_object_unref (context->theme);
+
+      context->theme = theme;
+
+      if (context->theme)
+        g_object_ref (context->theme);
+
+      g_signal_emit (context, signals[CHANGED], 0);
+    }
+}
+
+/**
+ * st_theme_context_get_theme:
+ * @context: a #StThemeContext
+ *
+ * Gets the default theme for the context. See st_theme_context_set_theme()
+ *
+ * Return value: (transfer none): the default theme for the context
+ */
+StTheme *
+st_theme_context_get_theme (StThemeContext *context)
+{
+  g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
+
+  return context->theme;
+}
+
+void
+st_theme_context_set_resolution (StThemeContext *context,
+                                 double          resolution)
+{
+  g_return_if_fail (ST_IS_THEME_CONTEXT (context));
+
+  context->resolution = resolution;
+}
+
+double
+st_theme_context_get_resolution (StThemeContext *context)
+{
+  g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), 96.);
+
+  return context->resolution;
+}
+
+void
+st_theme_context_set_font (StThemeContext             *context,
+                           const PangoFontDescription *font)
+{
+  g_return_if_fail (ST_IS_THEME_CONTEXT (context));
+
+  if (context->font == font)
+    return;
+
+  pango_font_description_free (context->font);
+  context->font = pango_font_description_copy (font);
+}
+
+const PangoFontDescription *
+st_theme_context_get_font (StThemeContext *context)
+{
+  g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
+
+  return context->font;
+}
+
+/**
+ * st_theme_context_get_root_node:
+ * @context: a #StThemeContext
+ *
+ * Gets the root node of the tree of theme style nodes that associated with this
+ * context. For the node tree associated with a stage, this node represents
+ * styles applied to the stage itself.
+ *
+ * Return value: (transfer none): the root node of the context's style tree
+ */
+StThemeNode *
+st_theme_context_get_root_node (StThemeContext *context)
+{
+  if (context->root_node == NULL)
+    context->root_node = st_theme_node_new (context, NULL, context->theme,
+                                            G_TYPE_NONE, NULL, NULL, NULL);
+
+  return context->root_node;
+}
diff --git a/src/st/st-theme-context.h b/src/st/st-theme-context.h
new file mode 100644
index 0000000..3a4e873
--- /dev/null
+++ b/src/st/st-theme-context.h
@@ -0,0 +1,40 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __ST_THEME_CONTEXT_H__
+#define __ST_THEME_CONTEXT_H__
+
+#include <clutter/clutter.h>
+#include <pango/pango.h>
+#include "st-theme-node.h"
+
+G_BEGIN_DECLS
+
+typedef struct _StThemeContextClass StThemeContextClass;
+
+#define ST_TYPE_THEME_CONTEXT             (st_theme_context_get_type ())
+#define ST_THEME_CONTEXT(object)          (G_TYPE_CHECK_INSTANCE_CAST ((object), ST_TYPE_THEME_CONTEXT, StThemeContext))
+#define ST_THEME_CONTEXT_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_THEME_CONTEXT, StThemeContextClass))
+#define ST_IS_THEME_CONTEXT(object)       (G_TYPE_CHECK_INSTANCE_TYPE ((object), ST_TYPE_THEME_CONTEXT))
+#define ST_IS_THEME_CONTEXT_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_THEME_CONTEXT))
+#define ST_THEME_CONTEXT_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_THEME_CONTEXT, StThemeContextClass))
+
+GType st_theme_context_get_type (void) G_GNUC_CONST;
+
+StThemeContext *st_theme_context_new           (void);
+StThemeContext *st_theme_context_get_for_stage (ClutterStage *stage);
+
+void                        st_theme_context_set_theme      (StThemeContext             *context,
+                                                             StTheme                    *theme);
+StTheme *                   st_theme_context_get_theme      (StThemeContext             *context);
+
+void                        st_theme_context_set_resolution (StThemeContext             *context,
+                                                             gdouble                     resolution);
+double                      st_theme_context_get_resolution (StThemeContext             *context);
+void                        st_theme_context_set_font       (StThemeContext             *context,
+                                                             const PangoFontDescription *font);
+const PangoFontDescription *st_theme_context_get_font       (StThemeContext             *context);
+
+StThemeNode *               st_theme_context_get_root_node  (StThemeContext             *context);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_CONTEXT_H__ */
diff --git a/src/st/st-theme-image.c b/src/st/st-theme-image.c
new file mode 100644
index 0000000..c70533f
--- /dev/null
+++ b/src/st/st-theme-image.c
@@ -0,0 +1,92 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <config.h>
+
+#include "st-theme-image.h"
+
+struct _StThemeImage {
+  GObject parent;
+
+  char *filename;
+  int border_top;
+  int border_right;
+  int border_bottom;
+  int border_left;
+};
+
+struct _StThemeImageClass {
+  GObjectClass parent_class;
+
+};
+
+G_DEFINE_TYPE (StThemeImage, st_theme_image, G_TYPE_OBJECT)
+
+static void
+st_theme_image_finalize (GObject *object)
+{
+  StThemeImage *image = ST_THEME_IMAGE (object);
+
+  g_free (image->filename);
+
+  G_OBJECT_CLASS (st_theme_image_parent_class)->finalize (object);
+}
+
+static void
+st_theme_image_class_init (StThemeImageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = st_theme_image_finalize;
+}
+
+static void
+st_theme_image_init (StThemeImage *image)
+{
+}
+
+StThemeImage *
+st_theme_image_new (const char *filename,
+                    int         border_top,
+                    int         border_right,
+                    int         border_bottom,
+                    int         border_left)
+{
+  StThemeImage *image;
+
+  image = g_object_new (ST_TYPE_THEME_IMAGE, NULL);
+
+  image->filename = g_strdup (filename);
+  image->border_top = border_top;
+  image->border_right = border_right;
+  image->border_bottom = border_bottom;
+  image->border_left = border_left;
+
+  return image;
+}
+
+const char *
+st_theme_image_get_filename (StThemeImage *image)
+{
+  g_return_val_if_fail (ST_IS_THEME_IMAGE (image), NULL);
+
+  return image->filename;
+}
+
+void
+st_theme_image_get_borders (StThemeImage *image,
+                            int          *border_top,
+                            int          *border_right,
+                            int          *border_bottom,
+                            int          *border_left)
+{
+  g_return_if_fail (ST_IS_THEME_IMAGE (image));
+
+  if (border_top)
+    *border_top = image->border_top;
+  if (border_right)
+    *border_right = image->border_right;
+  if (border_bottom)
+    *border_bottom = image->border_bottom;
+  if (border_left)
+    *border_left = image->border_left;
+}
diff --git a/src/st/st-theme-image.h b/src/st/st-theme-image.h
new file mode 100644
index 0000000..a069f65
--- /dev/null
+++ b/src/st/st-theme-image.h
@@ -0,0 +1,38 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __ST_THEME_IMAGE_H__
+#define __ST_THEME_IMAGE_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* A StThemeImage encapsulates an image with specified unscaled borders on each edge.
+ */
+typedef struct _StThemeImage      StThemeImage;
+typedef struct _StThemeImageClass StThemeImageClass;
+
+#define ST_TYPE_THEME_IMAGE             (st_theme_image_get_type ())
+#define ST_THEME_IMAGE(object)          (G_TYPE_CHECK_INSTANCE_CAST ((object), ST_TYPE_THEME_IMAGE, StThemeImage))
+#define ST_THEME_IMAGE_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_THEME_IMAGE, StThemeImageClass))
+#define ST_IS_THEME_IMAGE(object)       (G_TYPE_CHECK_INSTANCE_TYPE ((object), ST_TYPE_THEME_IMAGE))
+#define ST_IS_THEME_IMAGE_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_THEME_IMAGE))
+#define ST_THEME_IMAGE_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_THEME_IMAGE, StThemeImageClass))
+
+GType             st_theme_image_get_type          (void) G_GNUC_CONST;
+
+StThemeImage *st_theme_image_new (const char *filename,
+                                  int         border_top,
+                                  int         border_right,
+                                  int         border_bottom,
+                                  int         border_left);
+
+const char *st_theme_image_get_filename (StThemeImage *image);
+void        st_theme_image_get_borders  (StThemeImage *image,
+                                         int          *border_top,
+                                         int          *border_right,
+                                         int          *border_bottom,
+                                         int          *border_left);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_IMAGE_H__ */
diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c
new file mode 100644
index 0000000..4798699
--- /dev/null
+++ b/src/st/st-theme-node.c
@@ -0,0 +1,1777 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "st-theme-private.h"
+#include "st-theme-context.h"
+#include "st-theme-node.h"
+
+static void st_theme_node_init               (StThemeNode          *node);
+static void st_theme_node_class_init         (StThemeNodeClass     *klass);
+static void st_theme_node_dispose            (GObject                 *object);
+static void st_theme_node_finalize           (GObject                 *object);
+
+
+#if 0
+enum {
+  LAST_SIGNAL
+};
+
+static int signals[LAST_SIGNAL];
+#endif
+
+struct _StThemeNode {
+  GObject parent;
+
+  StThemeContext *context;
+  StThemeNode *parent_node;
+  StTheme *theme;
+
+  PangoFontDescription *font_desc;
+
+  ClutterColor background_color;
+  ClutterColor foreground_color;
+  ClutterColor border_color[4];
+  double border_width[4];
+  guint padding[4];
+
+  char *background_image;
+  StThemeImage *background_theme_image;
+
+  GType element_type;
+  char *element_id;
+  char *element_class;
+  char *pseudo_class;
+
+  CRDeclaration **properties;
+  int n_properties;
+
+  guint properties_computed : 1;
+  guint borders_computed : 1;
+  guint background_computed : 1;
+  guint foreground_computed : 1;
+  guint background_theme_image_computed : 1;
+  guint link_type : 2;
+};
+
+struct _StThemeNodeClass {
+  GObjectClass parent_class;
+
+};
+
+static const ClutterColor BLACK_COLOR = { 0, 0, 0, 0xff };
+static const ClutterColor TRANSPARENT_COLOR = { 0, 0, 0, 0 };
+
+G_DEFINE_TYPE (StThemeNode, st_theme_node, G_TYPE_OBJECT)
+
+static void
+st_theme_node_init (StThemeNode *node)
+{
+}
+
+static void
+st_theme_node_class_init (StThemeNodeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = st_theme_node_dispose;
+  object_class->finalize = st_theme_node_finalize;
+}
+
+static void
+st_theme_node_dispose (GObject *object)
+{
+  /* StThemeNode *node = ST_THEME_NODE (object); */
+
+  G_OBJECT_CLASS (st_theme_node_parent_class)->dispose (object);
+}
+
+static void
+st_theme_node_finalize (GObject *object)
+{
+  StThemeNode *node = ST_THEME_NODE (object);
+
+  g_free (node->element_id);
+  g_free (node->element_class);
+  g_free (node->pseudo_class);
+
+  if (node->properties)
+    {
+      g_free (node->properties);
+      node->properties = NULL;
+      node->n_properties = 0;
+    }
+
+  if (node->font_desc)
+    {
+      pango_font_description_free (node->font_desc);
+      node->font_desc = NULL;
+    }
+
+  if (node->background_theme_image)
+    {
+      g_object_unref (node->background_theme_image);
+      node->background_theme_image = NULL;
+    }
+
+  if (node->background_image)
+    g_free (node->background_image);
+
+  G_OBJECT_CLASS (st_theme_node_parent_class)->finalize (object);
+}
+
+StThemeNode *
+st_theme_node_new (StThemeContext    *context,
+                   StThemeNode       *parent_node,
+                   StTheme           *theme,
+                   GType              element_type,
+                   const char        *element_id,
+                   const char        *element_class,
+                   const char        *pseudo_class)
+{
+  StThemeNode *node;
+
+  g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
+  g_return_val_if_fail (parent_node == NULL || ST_IS_THEME_NODE (parent_node), NULL);
+
+  node = g_object_new (ST_TYPE_THEME_NODE, NULL);
+
+  node->context = g_object_ref (context);
+  if (parent_node != NULL)
+    node->parent_node = g_object_ref (parent_node);
+  else
+    node->parent_node = NULL;
+
+  if (theme == NULL && parent_node != NULL)
+    theme = parent_node->theme;
+
+  if (theme != NULL)
+    node->theme = g_object_ref (theme);
+
+  node->element_type = element_type;
+  node->element_id = g_strdup (element_id);
+  node->element_class = g_strdup (element_class);
+  node->pseudo_class = g_strdup (pseudo_class);
+
+  return node;
+}
+
+/**
+ * st_theme_node_get_parent:
+ * @node: a #StThemeNode
+ *
+ * Gets the parent themed element node.
+ *
+ * Return value: (transfer none): the parent #StThemeNode, or %NULL if this
+ *  is the root node of the tree of theme elements.
+ */
+StThemeNode *
+st_theme_node_get_parent (StThemeNode *node)
+{
+  g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+  return node->parent_node;
+}
+
+/**
+ * st_theme_node_get_theme:
+ * @node: a #StThemeNode
+ *
+ * Gets the theme stylesheet set that styles this node
+ *
+ * Return value: (transfer none): the theme stylesheet set
+ */
+StTheme *
+st_theme_node_get_theme (StThemeNode *node)
+{
+  g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+  return node->theme;
+}
+
+GType
+st_theme_node_get_element_type (StThemeNode *node)
+{
+  g_return_val_if_fail (ST_IS_THEME_NODE (node), G_TYPE_NONE);
+
+  return node->element_type;
+}
+
+const char *
+st_theme_node_get_element_id (StThemeNode *node)
+{
+  g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+  return node->element_id;
+}
+
+const char *
+st_theme_node_get_element_class (StThemeNode *node)
+{
+  g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+  return node->element_class;
+}
+
+const char *
+st_theme_node_get_pseudo_class (StThemeNode *node)
+{
+  g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+  return node->pseudo_class;
+}
+
+static void
+ensure_properties (StThemeNode *node)
+{
+  if (!node->properties_computed)
+    {
+      node->properties_computed = TRUE;
+
+      if (node->theme)
+        _st_theme_get_matched_properties (node->theme, node,
+                                          &node->properties, &node->n_properties);
+    }
+}
+
+typedef enum {
+  VALUE_FOUND,
+  VALUE_NOT_FOUND,
+  VALUE_INHERIT
+} GetFromTermResult;
+
+static gboolean
+term_is_inherit (CRTerm *term)
+{
+  return (term->type == TERM_IDENT &&
+          strcmp (term->content.str->stryng->str, "inherit") == 0);
+}
+
+static gboolean
+term_is_none (CRTerm *term)
+{
+  return (term->type == TERM_IDENT &&
+          strcmp (term->content.str->stryng->str, "none") == 0);
+}
+
+static gboolean
+term_is_transparent (CRTerm *term)
+{
+  return (term->type == TERM_IDENT &&
+          strcmp (term->content.str->stryng->str, "transparent") == 0);
+}
+
+static int
+color_component_from_double (double component)
+{
+  /* http://people.redhat.com/otaylor/pixel-converting.html */
+  if (component >= 1.0)
+    return 255;
+  else
+    return (int)(component * 256);
+}
+
+static GetFromTermResult
+get_color_from_rgba_term (CRTerm       *term,
+                          ClutterColor *color)
+{
+  CRTerm *arg = term->ext_content.func_param;
+  CRNum *num;
+  double r = 0, g = 0, b = 0, a = 0;
+  int i;
+
+  for (i = 0; i < 4; i++)
+    {
+      double value;
+
+      if (arg == NULL)
+        return VALUE_NOT_FOUND;
+
+      if ((i == 0 && arg->the_operator != NO_OP) ||
+          (i > 0 && arg->the_operator != COMMA))
+        return VALUE_NOT_FOUND;
+
+      if (arg->type != TERM_NUMBER)
+        return VALUE_NOT_FOUND;
+
+      num = arg->content.num;
+
+      /* For simplicity, we convert a,r,g,b to [0,1.0] floats and then
+       * convert them back below. Then when we set them on a cairo content
+       * we convert them back to floats, and then cairo converts them
+       * back to integers to pass them to X, and so forth...
+       */
+      if (i < 3)
+        {
+          if (num->type == NUM_PERCENTAGE)
+            {
+              value = num->val / 100;
+            }
+          else if (num->type == NUM_GENERIC)
+            {
+              value = num->val / 255;
+            }
+          else
+            {
+              return VALUE_NOT_FOUND;
+            }
+        }
+      else
+        {
+          if (num->type != NUM_GENERIC)
+            return VALUE_NOT_FOUND;
+
+          value = num->val;
+        }
+
+      value = CLAMP (value, 0, 1);
+
+      switch (i)
+        {
+        case 0:
+          r = value;
+          break;
+        case 1:
+          g = value;
+          break;
+        case 2:
+          b = value;
+          break;
+        case 3:
+          a = value;
+          break;
+        }
+
+      arg = arg->next;
+    }
+
+  color->red = color_component_from_double (r);
+  color->green = color_component_from_double (g);
+  color->blue = color_component_from_double (b);
+  color->alpha = color_component_from_double (a);
+
+  return VALUE_FOUND;
+}
+
+static GetFromTermResult
+get_color_from_term (StThemeNode  *node,
+                     CRTerm       *term,
+                     ClutterColor *color)
+{
+  CRRgb rgb;
+  enum CRStatus status;
+
+  /* Since libcroco doesn't know about rgba colors, it can't handle
+   * the transparent keyword
+   */
+  if (term_is_transparent (term))
+    {
+      *color = TRANSPARENT_COLOR;
+      return VALUE_FOUND;
+    }
+  /* rgba () colors - a CSS3 addition, are not supported by libcroco,
+   * but they are parsed as a "function", so we can emulate the
+   * functionality.
+   */
+  else if (term->type == TERM_FUNCTION &&
+           term->content.str &&
+           term->content.str->stryng &&
+           term->content.str->stryng->str &&
+           strcmp (term->content.str->stryng->str, "rgba") == 0)
+    {
+      return get_color_from_rgba_term (term, color);
+    }
+
+  status = cr_rgb_set_from_term (&rgb, term);
+  if (status != CR_OK)
+    return VALUE_NOT_FOUND;
+
+  if (rgb.inherit)
+    return VALUE_INHERIT;
+
+  if (rgb.is_percentage)
+    cr_rgb_compute_from_percentage (&rgb);
+
+  color->red = rgb.red;
+  color->green = rgb.green;
+  color->blue = rgb.blue;
+  color->alpha = 0xff;
+
+  return VALUE_FOUND;
+}
+
+gboolean
+st_theme_node_get_color (StThemeNode  *node,
+                         const char   *property_name,
+                         gboolean      inherit,
+                         ClutterColor *color)
+{
+
+  int i;
+
+  ensure_properties (node);
+
+  for (i = node->n_properties - 1; i >= 0; i--)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, property_name) == 0)
+        {
+          GetFromTermResult result = get_color_from_term (node, decl->value, color);
+          if (result == VALUE_FOUND)
+            {
+              return TRUE;
+            }
+          else if (result == VALUE_INHERIT)
+            {
+              if (node->parent_node)
+                return st_theme_node_get_color (node->parent_node, property_name, inherit, color);
+              else
+                break;
+            }
+        }
+    }
+
+  return FALSE;
+}
+
+gboolean
+st_theme_node_get_double (StThemeNode *node,
+                          const char  *property_name,
+                          gboolean     inherit,
+                          double      *value)
+{
+  gboolean result = FALSE;
+  int i;
+
+  ensure_properties (node);
+
+  for (i = node->n_properties - 1; i >= 0; i--)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, property_name) == 0)
+        {
+          CRTerm *term = decl->value;
+
+          if (term->type != TERM_NUMBER || term->content.num->type != NUM_GENERIC)
+            continue;
+
+          *value = term->content.num->val;
+          result = TRUE;
+          break;
+        }
+    }
+
+  if (!result && inherit && node->parent_node)
+    result = st_theme_node_get_double (node->parent_node, property_name, inherit, value);
+
+  return result;
+}
+
+static const PangoFontDescription *
+get_parent_font (StThemeNode *node)
+{
+  if (node->parent_node)
+    return st_theme_node_get_font (node->parent_node);
+  else
+    return st_theme_context_get_font (node->context);
+}
+
+static GetFromTermResult
+get_length_from_term (StThemeNode *node,
+                      CRTerm      *term,
+                      gboolean     use_parent_font,
+                      gdouble     *length)
+{
+  CRNum *num;
+
+  enum {
+    ABSOLUTE,
+    POINTS,
+    FONT_RELATIVE,
+  } type = ABSOLUTE;
+
+  double multiplier = 1.0;
+
+  if (term->type != TERM_NUMBER)
+    {
+      g_warning ("Ignoring length property that isn't a number");
+      return FALSE;
+    }
+
+  num = term->content.num;
+
+  switch (num->type)
+    {
+    case NUM_LENGTH_PX:
+      type = ABSOLUTE;
+      multiplier = 1;
+      break;
+    case NUM_LENGTH_PT:
+      type = POINTS;
+      multiplier = 1;
+      break;
+    case NUM_LENGTH_IN:
+      type = POINTS;
+      multiplier = 72;
+      break;
+    case NUM_LENGTH_CM:
+      type = POINTS;
+      multiplier = 72. / 2.54;
+      break;
+    case NUM_LENGTH_MM:
+      type = POINTS;
+      multiplier = 72. / 25.4;
+      break;
+    case NUM_LENGTH_PC:
+      type = POINTS;
+      multiplier = 12. / 25.4;
+      break;
+    case NUM_LENGTH_EM:
+      {
+        type = FONT_RELATIVE;
+        multiplier = 1;
+        break;
+      }
+    case NUM_LENGTH_EX:
+      {
+        /* Doing better would require actually resolving the font description
+         * to a specific font, and Pango doesn't have an ex metric anyways,
+         * so we'd have to try and synthesize it by complicated means.
+         *
+         * The 0.5em is the CSS spec suggested thing to use when nothing
+         * better is available.
+         */
+        type = FONT_RELATIVE;
+        multiplier = 0.5;
+        break;
+      }
+
+    case NUM_INHERIT:
+      return VALUE_INHERIT;
+
+    case NUM_AUTO:
+      g_warning ("'auto' not supported for lengths");
+      return VALUE_NOT_FOUND;
+
+    case NUM_GENERIC:
+      g_warning ("length values must specify a unit");
+      return VALUE_NOT_FOUND;
+
+    case NUM_PERCENTAGE:
+      g_warning ("percentage lengths not currently supported");
+      return VALUE_NOT_FOUND;
+
+    case NUM_ANGLE_DEG:
+    case NUM_ANGLE_RAD:
+    case NUM_ANGLE_GRAD:
+    case NUM_TIME_MS:
+    case NUM_TIME_S:
+    case NUM_FREQ_HZ:
+    case NUM_FREQ_KHZ:
+    case NUM_UNKNOWN_TYPE:
+    case NB_NUM_TYPE:
+      g_warning ("Ignoring invalid type of number of length property");
+      return VALUE_NOT_FOUND;
+    }
+
+  switch (type)
+    {
+    case ABSOLUTE:
+      *length = num->val * multiplier;
+      break;
+    case POINTS:
+      {
+        double resolution = st_theme_context_get_resolution (node->context);
+        *length = num->val * multiplier * (resolution / 72.);
+      }
+      break;
+    case FONT_RELATIVE:
+      {
+        const PangoFontDescription *desc;
+        double font_size;
+
+        if (use_parent_font)
+          desc = get_parent_font (node);
+        else
+          desc = st_theme_node_get_font (node);
+
+        font_size = (double)pango_font_description_get_size (desc) / PANGO_SCALE;
+
+        if (pango_font_description_get_size_is_absolute (desc))
+          {
+            *length = num->val * multiplier * font_size;
+          }
+        else
+          {
+            double resolution = st_theme_context_get_resolution (node->context);
+            *length = num->val * multiplier * (resolution / 72.) * font_size;
+          }
+      }
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+
+  return VALUE_FOUND;
+}
+
+static GetFromTermResult
+get_length_internal (StThemeNode *node,
+                     const char  *property_name,
+                     const char  *suffixed,
+                     gdouble     *length)
+{
+  int i;
+
+  ensure_properties (node);
+
+  for (i = node->n_properties - 1; i >= 0; i--)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, property_name) == 0 ||
+          (suffixed != NULL && strcmp (decl->property->stryng->str, suffixed) == 0))
+        {
+          GetFromTermResult result = get_length_from_term (node, decl->value, FALSE, length);
+          if (result != VALUE_NOT_FOUND)
+            return result;
+        }
+    }
+
+  return VALUE_NOT_FOUND;
+}
+
+gboolean
+st_theme_node_get_length (StThemeNode *node,
+                          const char  *property_name,
+                          gboolean     inherit,
+                          gdouble     *length)
+{
+  GetFromTermResult result = get_length_internal (node, property_name, NULL, length);
+  if (result == VALUE_FOUND)
+    return TRUE;
+  else if (result == VALUE_INHERIT)
+    inherit = TRUE;
+
+  if (inherit && node->parent_node &&
+      st_theme_node_get_length (node->parent_node, property_name, inherit, length))
+    return TRUE;
+  else
+    return FALSE;
+}
+
+static void
+do_border_property (StThemeNode   *node,
+                    CRDeclaration *decl)
+{
+  const char *property_name = decl->property->stryng->str + 6; /* Skip 'border' */
+  StSide side = (StSide)-1;
+  ClutterColor color;
+  gboolean color_set = FALSE;
+  double width;
+  gboolean width_set = FALSE;
+  int j;
+
+  if (g_str_has_prefix (property_name, "-left"))
+    {
+      side = ST_SIDE_LEFT;
+      property_name += 5;
+    }
+  else if (g_str_has_prefix (property_name, "-right"))
+    {
+      side = ST_SIDE_RIGHT;
+      property_name += 6;
+    }
+  else if (g_str_has_prefix (property_name, "-top"))
+    {
+      side = ST_SIDE_TOP;
+      property_name += 4;
+    }
+  else if (g_str_has_prefix (property_name, "-bottom"))
+    {
+      side = ST_SIDE_BOTTOM;
+      property_name += 7;
+    }
+
+  if (strcmp (property_name, "") == 0)
+    {
+      /* Set value for width/color/node in any order */
+      CRTerm *term;
+
+      for (term = decl->value; term; term = term->next)
+        {
+          GetFromTermResult result;
+
+          if (term->type == TERM_IDENT)
+            {
+              const char *ident = term->content.str->stryng->str;
+              if (strcmp (ident, "none") == 0 || strcmp (ident, "hidden") == 0)
+                {
+                  width = 0.;
+                  continue;
+                }
+              else if (strcmp (ident, "solid") == 0)
+                {
+                  /* The only thing we support */
+                  continue;
+                }
+              else if (strcmp (ident, "dotted") == 0 ||
+                       strcmp (ident, "dashed") == 0 ||
+                       strcmp (ident, "solid") == 0 ||
+                       strcmp (ident, "double") == 0 ||
+                       strcmp (ident, "groove") == 0 ||
+                       strcmp (ident, "ridge") == 0 ||
+                       strcmp (ident, "inset") == 0 ||
+                       strcmp (ident, "outset") == 0)
+                {
+                  /* Treat the same as solid */
+                  continue;
+                }
+
+              /* Presumably a color, fall through */
+            }
+
+          if (term->type == TERM_NUMBER)
+            {
+              result = get_length_from_term (node, term, FALSE, &width);
+              if (result != VALUE_NOT_FOUND)
+                {
+                  width_set = result == VALUE_FOUND;
+                  continue;
+                }
+            }
+
+          result = get_color_from_term (node, term, &color);
+          if (result != VALUE_NOT_FOUND)
+            {
+              color_set = result == VALUE_FOUND;
+              continue;
+            }
+        }
+
+    }
+  else if (strcmp (property_name, "-color") == 0)
+    {
+      if (decl->value == NULL || decl->value->next != NULL)
+        return;
+
+      if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
+        { /* Ignore inherit */
+          color_set = TRUE;
+        }
+    }
+  else if (strcmp (property_name, "-width") == 0)
+    {
+      if (decl->value == NULL || decl->value->next != NULL)
+        return;
+
+      if (get_length_from_term (node, decl->value, FALSE, &width) == VALUE_FOUND)
+        { /* Ignore inherit */
+          width_set = TRUE;
+        }
+    }
+
+  if (side == (StSide)-1)
+    {
+      for (j = 0; j < 4; j++)
+        {
+          if (color_set)
+            node->border_color[j] = color;
+          if (width_set)
+            node->border_width[j] = width;
+        }
+    }
+  else
+    {
+      if (color_set)
+        node->border_color[side] = color;
+      if (width_set)
+        node->border_width[side] = width;
+    }
+}
+
+static void
+do_padding_property_term (StThemeNode *node,
+                          CRTerm      *term,
+                          gboolean     left,
+                          gboolean     right,
+                          gboolean     top,
+                          gboolean     bottom)
+{
+  gdouble value;
+
+  if (get_length_from_term (node, term, FALSE, &value) != VALUE_FOUND)
+    return;
+
+  if (left)
+    node->padding[ST_SIDE_LEFT] = value;
+  if (right)
+    node->padding[ST_SIDE_RIGHT] = value;
+  if (top)
+    node->padding[ST_SIDE_TOP] = value;
+  if (bottom)
+    node->padding[ST_SIDE_BOTTOM] = value;
+}
+
+static void
+do_padding_property (StThemeNode   *node,
+                     CRDeclaration *decl)
+{
+  const char *property_name = decl->property->stryng->str + 7; /* Skip 'padding' */
+
+  if (strcmp (property_name, "") == 0)
+    {
+      /* Slight deviation ... if we don't understand some of the terms and understand others,
+       * then we set the ones we understand and ignore the others instead of ignoring the
+       * whole thing
+       */
+      if (decl->value == NULL) /* 0 values */
+        return;
+      else if (decl->value->next == NULL)
+        { /* 1 value */
+          do_padding_property_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* left/right/top/bottom */
+          return;
+        }  else if (decl->value->next->next == NULL)
+        { /* 2 values */
+          do_padding_property_term (node, decl->value,       FALSE, FALSE, TRUE,  TRUE);  /* top/bottom */
+          do_padding_property_term (node, decl->value->next, TRUE, TRUE,   FALSE, FALSE); /* left/right */
+        }  else if (decl->value->next->next->next == NULL)
+        { /* 3 values */
+          do_padding_property_term (node, decl->value,             FALSE, FALSE, TRUE,  FALSE); /* top */
+          do_padding_property_term (node, decl->value->next,       TRUE,  TRUE,  FALSE, FALSE); /* left/right */
+          do_padding_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE);  /* bottom */
+        }
+      else  if (decl->value->next->next->next == NULL)
+        { /* 4 values */
+          do_padding_property_term (node, decl->value,                   FALSE, FALSE, TRUE,  FALSE);
+          do_padding_property_term (node, decl->value->next,             FALSE, TRUE, FALSE, FALSE); /* left */
+          do_padding_property_term (node, decl->value->next->next,       FALSE, FALSE, FALSE, TRUE);
+          do_padding_property_term (node, decl->value->next->next->next, TRUE,  FALSE, FALSE, TRUE); /* left */
+        }
+      else
+        {
+          g_warning ("Too many values for padding property");
+          return;
+        }
+    }
+  else
+    {
+      if (decl->value == NULL || decl->value->next != NULL)
+        return;
+
+      if (strcmp (property_name, "-left") == 0)
+        {
+          do_padding_property_term (node, decl->value, TRUE,  FALSE, FALSE, FALSE);
+        }
+      else if (strcmp (property_name, "-right") == 0)
+        {
+          do_padding_property_term (node, decl->value, FALSE, TRUE,  FALSE, FALSE);
+        }
+      else if (strcmp (property_name, "-top") == 0)
+        {
+          do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE,  FALSE);
+        }
+      else if (strcmp (property_name, "-bottom") == 0)
+        {
+          do_padding_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
+        }
+    }
+}
+
+static void
+ensure_borders (StThemeNode *node)
+{
+  int i, j;
+
+  if (node->borders_computed)
+    return;
+
+  node->borders_computed = TRUE;
+
+  ensure_properties (node);
+
+  for (j = 0; j < 4; j++)
+    {
+      node->border_width[j] = 0;
+      node->border_color[j] = TRANSPARENT_COLOR;
+    }
+
+  for (i = 0; i < node->n_properties; i++)
+    {
+      CRDeclaration *decl = node->properties[i];
+      const char *property_name = decl->property->stryng->str;
+
+      if (g_str_has_prefix (property_name, "border"))
+        {
+          do_border_property (node, decl);
+        }
+      else if (g_str_has_prefix (property_name, "padding"))
+        {
+          do_padding_property (node, decl);
+        }
+    }
+}
+
+double
+st_theme_node_get_border_width (StThemeNode *node,
+                                   StSide       side)
+{
+  g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
+  g_return_val_if_fail (side >= ST_SIDE_LEFT && side <= ST_SIDE_BOTTOM, 0.);
+
+  ensure_borders (node);
+
+  return node->border_width[side];
+}
+
+static GetFromTermResult
+get_background_color_from_term (StThemeNode  *node,
+                                CRTerm       *term,
+                                ClutterColor *color)
+{
+  GetFromTermResult result = get_color_from_term (node, term, color);
+  if (result == VALUE_NOT_FOUND)
+    {
+      if (term_is_transparent (term))
+        {
+          *color = TRANSPARENT_COLOR;
+          return VALUE_FOUND;
+        }
+    }
+
+  return result;
+}
+
+static void
+ensure_background (StThemeNode *node)
+{
+  int i;
+
+  if (node->background_computed)
+    return;
+
+  node->background_computed = TRUE;
+  node->background_color = TRANSPARENT_COLOR;
+
+  ensure_properties (node);
+
+  for (i = 0; i < node->n_properties; i++)
+    {
+      CRDeclaration *decl = node->properties[i];
+      const char *property_name = decl->property->stryng->str;
+
+      if (g_str_has_prefix (property_name, "background"))
+        property_name += 10;
+      else
+        continue;
+
+      if (strcmp (property_name, "") == 0)
+        {
+          /* We're very liberal here ... if we recognize any term in the expression we take it, and
+           * we ignore the rest. The actual specification is:
+           *
+           * background: [<'background-color'> || <'background-image'> || <'background-repeat'> || <'background-attachment'> || <'background-position'>] | inherit
+           */
+
+          CRTerm *term;
+          /* background: property sets all terms to specified or default values */
+          node->background_color = TRANSPARENT_COLOR;
+          g_free (node->background_image);
+          node->background_image = NULL;
+
+          for (term = decl->value; term; term = term->next)
+            {
+              GetFromTermResult result = get_background_color_from_term (node, term, &node->background_color);
+              if (result == VALUE_FOUND)
+                {
+                }
+              else if (result == VALUE_INHERIT)
+                {
+                  if (node->parent_node)
+                    {
+                      st_theme_node_get_background_color (node->parent_node, &node->background_color);
+                      node->background_image = g_strdup (st_theme_node_get_background_image (node->parent_node));
+                    }
+                }
+              else if (term_is_none (term))
+                {
+                }
+              else if (term->type == TERM_URI)
+                {
+                  node->background_image = _st_theme_resolve_url (node->theme,
+                                                                     decl->parent_statement->parent_sheet,
+                                                                     term->content.str->stryng->str);
+                }
+            }
+        }
+      else if (strcmp (property_name, "-color") == 0)
+        {
+          GetFromTermResult result;
+
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          result = get_background_color_from_term (node, decl->value, &node->background_color);
+          if (result == VALUE_FOUND)
+            {
+            }
+          else if (result == VALUE_INHERIT)
+            {
+              if (node->parent_node)
+                st_theme_node_get_background_color (node->parent_node, &node->background_color);
+            }
+        }
+      else if (strcmp (property_name, "-image") == 0)
+        {
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          if (decl->value->type == TERM_URI)
+            {
+              g_free (node->background_image);
+              node->background_image = _st_theme_resolve_url (node->theme,
+                                                                 decl->parent_statement->parent_sheet,
+                                                                 decl->value->content.str->stryng->str);
+            }
+          else if (term_is_inherit (decl->value))
+            {
+              g_free (node->background_image);
+              node->background_image = g_strdup (st_theme_node_get_background_image (node->parent_node));
+            }
+          else if (term_is_none (decl->value))
+            {
+              g_free (node->background_image);
+              node->background_image = NULL;
+            }
+        }
+    }
+}
+
+void
+st_theme_node_get_background_color (StThemeNode  *node,
+                                    ClutterColor *color)
+{
+  g_return_if_fail (ST_IS_THEME_NODE (node));
+
+  ensure_background (node);
+
+  *color = node->background_color;
+}
+
+const char *
+st_theme_node_get_background_image (StThemeNode *node)
+{
+  g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+  ensure_background (node);
+
+  return node->background_image;
+}
+
+void
+st_theme_node_get_foreground_color (StThemeNode  *node,
+                                    ClutterColor *color)
+{
+  g_return_if_fail (ST_IS_THEME_NODE (node));
+
+  if (!node->foreground_computed)
+    {
+      int i;
+
+      node->foreground_computed = TRUE;
+
+      ensure_properties (node);
+
+      for (i = node->n_properties - 1; i >= 0; i--)
+        {
+          CRDeclaration *decl = node->properties[i];
+
+          if (strcmp (decl->property->stryng->str, "color") == 0)
+            {
+              GetFromTermResult result = get_color_from_term (node, decl->value, &node->foreground_color);
+              if (result == VALUE_FOUND)
+                goto out;
+              else if (result == VALUE_INHERIT)
+                break;
+            }
+        }
+
+      if (node->parent_node)
+        st_theme_node_get_foreground_color (node->parent_node, &node->foreground_color);
+      else
+        node->foreground_color = BLACK_COLOR; /* default to black */
+    }
+
+ out:
+  *color = node->foreground_color;
+}
+
+void
+st_theme_node_get_border_color (StThemeNode  *node,
+                                StSide        side,
+                                ClutterColor *color)
+{
+  g_return_if_fail (ST_IS_THEME_NODE (node));
+  g_return_if_fail (side >= ST_SIDE_LEFT && side <= ST_SIDE_BOTTOM);
+
+  ensure_borders (node);
+
+  *color = node->border_color[side];
+}
+
+double
+st_theme_node_get_padding (StThemeNode *node,
+                           StSide       side)
+{
+  g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
+  g_return_val_if_fail (side >= ST_SIDE_LEFT && side <= ST_SIDE_BOTTOM, 0.);
+
+  ensure_borders (node);
+
+  return node->padding[side];
+}
+
+StTextDecoration
+st_theme_node_get_text_decoration (StThemeNode *node)
+{
+  int i;
+
+  ensure_properties (node);
+
+  for (i = node->n_properties - 1; i >= 0; i--)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, "text-decoration") == 0)
+        {
+          CRTerm *term = decl->value;
+          StTextDecoration decoration = 0;
+
+          /* Specification is none | [ underline || overline || line-through || blink ] | inherit
+           *
+           * We're a bit more liberal, and for example treat 'underline none' as the same as
+           * none.
+           */
+          for (; term; term = term->next)
+            {
+              if (term->type != TERM_IDENT)
+                goto next_decl;
+
+              if (strcmp (term->content.str->stryng->str, "none") == 0)
+                {
+                  return 0;
+                }
+              else if (strcmp (term->content.str->stryng->str, "inherit") == 0)
+                {
+                  if (node->parent_node)
+                    return st_theme_node_get_text_decoration (node->parent_node);
+                }
+              else if (strcmp (term->content.str->stryng->str, "underline") == 0)
+                {
+                  decoration |= ST_TEXT_DECORATION_UNDERLINE;
+                }
+              else if (strcmp (term->content.str->stryng->str, "overline") == 0)
+                {
+                  decoration |= ST_TEXT_DECORATION_OVERLINE;
+                }
+              else if (strcmp (term->content.str->stryng->str, "line-through") == 0)
+                {
+                  decoration |= ST_TEXT_DECORATION_LINE_THROUGH;
+                }
+              else if (strcmp (term->content.str->stryng->str, "blink") == 0)
+                {
+                  decoration |= ST_TEXT_DECORATION_BLINK;
+                }
+              else
+                {
+                  goto next_decl;
+                }
+            }
+
+          return decoration;
+        }
+
+    next_decl:
+      ;
+    }
+
+  return 0;
+}
+
+static gboolean
+font_family_from_terms (CRTerm *term,
+                        char  **family)
+{
+  GString *family_string;
+  gboolean result = FALSE;
+  gboolean last_was_quoted = FALSE;
+
+  if (!term)
+    return FALSE;
+
+  family_string = g_string_new (NULL);
+
+  while (term)
+    {
+      if (term->type != TERM_STRING && term->type != TERM_IDENT)
+        {
+          goto out;
+        }
+
+      if (family_string->len > 0)
+        {
+          if (term->the_operator != COMMA && term->the_operator != NO_OP)
+            goto out;
+          /* Can concetenate to bare words, but not two quoted strings */
+          if ((term->the_operator == NO_OP && last_was_quoted) || term->type == TERM_STRING)
+            goto out;
+
+          if (term->the_operator == NO_OP)
+            {
+              g_string_append (family_string, " ");
+            }
+          else
+            {
+              g_string_append (family_string, ", ");
+            }
+        }
+      else
+        {
+          if (term->the_operator != NO_OP)
+            goto out;
+        }
+
+      g_string_append (family_string, term->content.str->stryng->str);
+
+      term = term->next;
+    }
+
+  result = TRUE;
+
+ out:
+  if (result)
+    {
+      *family = g_string_free (family_string, FALSE);
+      return TRUE;
+    }
+  else
+    {
+      *family = g_string_free (family_string, TRUE);
+      return FALSE;
+    }
+}
+
+/* In points */
+static int font_sizes[] = {
+  6 * 1024,   /* xx-small */
+  8 * 1024,   /* x-small */
+  10 * 1024,  /* small */
+  12 * 1024,  /* medium */
+  16 * 1024,  /* large */
+  20 * 1024,  /* x-large */
+  24 * 1024,  /* xx-large */
+};
+
+static gboolean
+font_size_from_term (StThemeNode *node,
+                     CRTerm      *term,
+                     double      *size)
+{
+  if (term->type == TERM_IDENT)
+    {
+      double resolution = st_theme_context_get_resolution (node->context);
+      /* We work in integers to avoid double comparisons when converting back
+       * from a size in pixels to a logical size.
+       */
+      int size_points = (int)(0.5 + *size * (72. / resolution));
+
+      if (strcmp (term->content.str->stryng->str, "xx-small") == 0)
+        {
+          size_points = font_sizes[0];
+        }
+      else if (strcmp (term->content.str->stryng->str, "x-small") == 0)
+        {
+          size_points = font_sizes[1];
+        }
+      else if (strcmp (term->content.str->stryng->str, "small") == 0)
+        {
+          size_points = font_sizes[2];
+        }
+      else if (strcmp (term->content.str->stryng->str, "medium") == 0)
+        {
+          size_points = font_sizes[3];
+        }
+      else if (strcmp (term->content.str->stryng->str, "large") == 0)
+        {
+          size_points = font_sizes[4];
+        }
+      else if (strcmp (term->content.str->stryng->str, "x-large") == 0)
+        {
+          size_points = font_sizes[5];
+        }
+      else if (strcmp (term->content.str->stryng->str, "xx-large") == 0)
+        {
+          size_points = font_sizes[6];
+        }
+      else if (strcmp (term->content.str->stryng->str, "smaller") == 0)
+        {
+          /* Find the standard size equal to or smaller than the current size */
+          int i = 0;
+
+          while (i <= 6 && font_sizes[i] < size_points)
+            i++;
+
+          if (i > 6)
+            { /* original size greater than any standard size */
+              size_points = (int)(0.5 + size_points / 1.2);
+            }
+          else
+            {
+              /* Go one smaller than that, if possible */
+              if (i > 0)
+                i--;
+
+              size_points = font_sizes[i];
+            }
+        }
+      else if (strcmp (term->content.str->stryng->str, "larger") == 0)
+        {
+          /* Find the standard size equal to or larger than the current size */
+          int i = 6;
+
+          while (i >= 0 && font_sizes[i] > size_points)
+            i--;
+
+          if (i < 0) /* original size smaller than any standard size */
+            i = 0;
+
+          /* Go one larger than that, if possible */
+          if (i < 6)
+            i++;
+
+          size_points = font_sizes[i];
+        }
+      else
+        {
+          return FALSE;
+        }
+
+      *size = size_points * (resolution / 72.);
+      return TRUE;
+
+    }
+  else if (term->type == TERM_NUMBER && term->content.num->type == NUM_PERCENTAGE)
+    {
+      *size *= term->content.num->val / 100.;
+      return TRUE;
+    }
+  else if (get_length_from_term (node, term, TRUE, size) == VALUE_FOUND)
+    {
+      /* Convert from pixels to Pango units */
+      *size *= 1024;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+font_weight_from_term (CRTerm      *term,
+                       PangoWeight *weight,
+                       gboolean    *weight_absolute)
+{
+  if (term->type == TERM_NUMBER)
+    {
+      int weight_int;
+
+      /* The spec only allows numeric weights from 100-900, though Pango
+       * will handle any number. We just let anything through.
+       */
+      if (term->content.num->type != NUM_GENERIC)
+        return FALSE;
+
+      weight_int = (int)(term->content.num->val + 0.5);
+
+      *weight = weight_int;
+      *weight_absolute = TRUE;
+
+    }
+  else if (term->type == TERM_IDENT)
+    {
+      /* FIXME: handle INHERIT */
+
+      if (strcmp (term->content.str->stryng->str, "bold") == 0)
+        {
+          *weight = PANGO_WEIGHT_BOLD;
+          *weight_absolute = TRUE;
+        }
+      else if (strcmp (term->content.str->stryng->str, "normal") == 0)
+        {
+          *weight = PANGO_WEIGHT_NORMAL;
+          *weight_absolute = TRUE;
+        }
+      else if (strcmp (term->content.str->stryng->str, "bolder") == 0)
+        {
+          *weight = PANGO_WEIGHT_BOLD;
+          *weight_absolute = FALSE;
+        }
+      else if (strcmp (term->content.str->stryng->str, "lighter") == 0)
+        {
+          *weight = PANGO_WEIGHT_LIGHT;
+          *weight_absolute = FALSE;
+        }
+      else
+        {
+          return FALSE;
+        }
+
+    }
+  else
+    {
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+font_style_from_term (CRTerm     *term,
+                      PangoStyle *style)
+{
+  if (term->type != TERM_IDENT)
+    return FALSE;
+
+  /* FIXME: handle INHERIT */
+
+  if (strcmp (term->content.str->stryng->str, "normal") == 0)
+    *style = PANGO_STYLE_NORMAL;
+  else if (strcmp (term->content.str->stryng->str, "oblique") == 0)
+    *style = PANGO_STYLE_OBLIQUE;
+  else if (strcmp (term->content.str->stryng->str, "italic") == 0)
+    *style = PANGO_STYLE_ITALIC;
+  else
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+font_variant_from_term (CRTerm       *term,
+                        PangoVariant *variant)
+{
+  if (term->type != TERM_IDENT)
+    return FALSE;
+
+  /* FIXME: handle INHERIT */
+
+  if (strcmp (term->content.str->stryng->str, "normal") == 0)
+    *variant = PANGO_VARIANT_NORMAL;
+  else if (strcmp (term->content.str->stryng->str, "small-caps") == 0)
+    *variant = PANGO_VARIANT_SMALL_CAPS;
+  else
+    return FALSE;
+
+  return TRUE;
+}
+
+const PangoFontDescription *
+st_theme_node_get_font (StThemeNode *node)
+{
+  PangoStyle font_style;
+  gboolean font_style_set = FALSE;
+  PangoVariant variant;
+  gboolean variant_set = FALSE;
+  PangoWeight weight;
+  gboolean weight_absolute;
+  gboolean weight_set = FALSE;
+  double parent_size;
+  double size = 0.; /* Suppress warning */
+  gboolean size_set = FALSE;
+  char *family = NULL;
+  int i;
+
+  if (node->font_desc)
+    return node->font_desc;
+
+  node->font_desc = pango_font_description_copy (get_parent_font (node));
+  parent_size = pango_font_description_get_size (node->font_desc);
+  if (!pango_font_description_get_size_is_absolute (node->font_desc))
+    {
+      double resolution = st_theme_context_get_resolution (node->context);
+      parent_size *= (resolution / 72.);
+    }
+
+  ensure_properties (node);
+
+  for (i = 0; i < node->n_properties; i++)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, "font") == 0)
+        {
+          PangoStyle tmp_style = PANGO_STYLE_NORMAL;
+          PangoVariant tmp_variant = PANGO_VARIANT_NORMAL;
+          PangoWeight tmp_weight = PANGO_WEIGHT_NORMAL;
+          gboolean tmp_weight_absolute = TRUE;
+          double tmp_size;
+          CRTerm *term = decl->value;
+
+          /* A font specification starts with node/variant/weight
+           * in any order. Each is allowed to be specified only once,
+           * but we don't enforce that.
+           */
+          for (; term; term = term->next)
+            {
+              if (font_style_from_term (term, &tmp_style))
+                ;
+              else if (font_variant_from_term (term, &tmp_variant))
+                ;
+              else if (font_weight_from_term (term, &tmp_weight, &tmp_weight_absolute))
+                ;
+              else
+                break;
+            }
+
+          /* The size is mandatory */
+
+          if (term == NULL || term->type != TERM_NUMBER)
+            {
+              g_warning ("Size missing from font property");
+              continue;
+            }
+
+          tmp_size = parent_size;
+          if (!font_size_from_term (node, term, &tmp_size))
+            {
+              g_warning ("Couldn't parse size in font property");
+              continue;
+            }
+
+          term = term->next;
+
+          if (term != NULL && term->type && TERM_NUMBER && term->the_operator == DIVIDE)
+            {
+              /* Ignore line-height specification */
+              term = term->next;
+            }
+
+          /* the font family is mandatory - it is a comma-separated list of
+           * names.
+           */
+          if (!font_family_from_terms (term, &family))
+            {
+              g_warning ("Couldn't parse family in font property");
+              continue;
+            }
+
+          font_style = tmp_style;
+          font_style_set = TRUE;
+          weight = tmp_weight;
+          weight_absolute = tmp_weight_absolute;
+          weight_set = TRUE;
+          variant = tmp_variant;
+          variant_set = TRUE;
+
+          size = tmp_size;
+          size_set = TRUE;
+
+        }
+      else if (strcmp (decl->property->stryng->str, "font-family") == 0)
+        {
+          if (!font_family_from_terms (decl->value, &family))
+            {
+              g_warning ("Couldn't parse family in font property");
+              continue;
+            }
+        }
+      else if (strcmp (decl->property->stryng->str, "font-weight") == 0)
+        {
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          if (font_weight_from_term (decl->value, &weight, &weight_absolute))
+            weight_set = TRUE;
+        }
+      else if (strcmp (decl->property->stryng->str, "font-style") == 0)
+        {
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          if (font_style_from_term (decl->value, &font_style))
+            font_style_set = TRUE;
+        }
+      else if (strcmp (decl->property->stryng->str, "font-variant") == 0)
+        {
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          if (font_variant_from_term (decl->value, &variant))
+            variant_set = TRUE;
+        }
+      else if (strcmp (decl->property->stryng->str, "font-size") == 0)
+        {
+          gdouble tmp_size;
+          if (decl->value == NULL || decl->value->next != NULL)
+            continue;
+
+          tmp_size = parent_size;
+          if (font_size_from_term (node, decl->value, &tmp_size))
+            {
+              size = tmp_size;
+              size_set = TRUE;
+            }
+        }
+    }
+
+  if (family)
+    pango_font_description_set_family (node->font_desc, family);
+
+  if (size_set)
+    pango_font_description_set_absolute_size (node->font_desc, size);
+
+  if (weight_set)
+    {
+      if (!weight_absolute)
+        {
+          /* bolder/lighter are supposed to switch between available styles, but with
+           * font substitution, that gets to be a pretty fuzzy concept. So we use
+           * a fixed step of 200. (The spec says 100, but that might not take us from
+           * normal to bold.
+           */
+
+          PangoWeight old_weight = pango_font_description_get_weight (node->font_desc);
+          if (weight == PANGO_WEIGHT_BOLD)
+            weight = old_weight + 200;
+          else
+            weight = old_weight - 200;
+
+          if (weight < 100)
+            weight = 100;
+          if (weight > 900)
+            weight = 900;
+        }
+
+      pango_font_description_set_weight (node->font_desc, weight);
+    }
+
+  if (font_style_set)
+    pango_font_description_set_style (node->font_desc, font_style);
+  if (variant_set)
+    pango_font_description_set_variant (node->font_desc, variant);
+
+  return node->font_desc;
+}
+
+/**
+ * st_theme_node_get_background_theme_image:
+ * @node: a #StThemeNode
+ *
+ * Gets the value for the -st-background-image style property
+ *
+ * Return value: (transfer none): the background image, or %NULL
+ *   if there is no background theme image.
+ */
+StThemeImage *
+st_theme_node_get_background_theme_image (StThemeNode *node)
+{
+  int i;
+
+  if (node->background_theme_image_computed)
+    return node->background_theme_image;
+
+  node->background_theme_image = NULL;
+  node->background_theme_image_computed = TRUE;
+
+  ensure_properties (node);
+
+  for (i = node->n_properties - 1; i >= 0; i--)
+    {
+      CRDeclaration *decl = node->properties[i];
+
+      if (strcmp (decl->property->stryng->str, "-st-background-image") == 0)
+        {
+          CRTerm *term = decl->value;
+          int lengths[4];
+          int n_lengths = 0;
+          int i;
+
+          const char *url;
+          int border_top;
+          int border_right;
+          int border_bottom;
+          int border_left;
+
+          char *filename;
+
+          /* First term must be the URL to the image */
+          if (term->type != TERM_URI)
+            goto next_property;
+
+          url = term->content.str->stryng->str;
+
+          term = term->next;
+
+          /* Followed by 0 to 4 lengths */
+          for (i = 0; i < 4; i++)
+            {
+              double value;
+
+              if (term == NULL)
+                break;
+
+              if (get_length_from_term (node, term, FALSE, &value) != VALUE_FOUND)
+                goto next_property;
+
+              lengths[n_lengths] = (int)(0.5 + value);
+              n_lengths++;
+
+              term = term->next;
+            }
+
+          switch (n_lengths)
+            {
+            case 0:
+              border_top = border_right = border_bottom = border_left = 0;
+              break;
+            case 1:
+              border_top = border_right = border_bottom = border_left = lengths[0];
+              break;
+            case 2:
+              border_top = border_bottom = lengths[0];
+              border_left = border_right = lengths[1];
+              break;
+            case 3:
+              border_top = lengths[0];
+              border_left = border_right = lengths[1];
+              border_bottom = lengths[2];
+              break;
+            case 4:
+            default:
+              border_top = lengths[0];
+              border_right = lengths[1];
+              border_bottom = lengths[2];
+              border_left = lengths[3];
+              break;
+            }
+
+          filename = _st_theme_resolve_url (node->theme, decl->parent_statement->parent_sheet, url);
+          if (filename == NULL)
+            goto next_property;
+
+          node->background_theme_image = st_theme_image_new (filename,
+                                                             border_top, border_right, border_bottom, border_left);
+
+          g_free (filename);
+
+          return node->background_theme_image;
+        }
+
+    next_property:
+      ;
+    }
+
+  return NULL;
+}
diff --git a/src/st/st-theme-node.h b/src/st/st-theme-node.h
new file mode 100644
index 0000000..f226665
--- /dev/null
+++ b/src/st/st-theme-node.h
@@ -0,0 +1,115 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __ST_THEME_NODE_H__
+#define __ST_THEME_NODE_H__
+
+#include <clutter/clutter.h>
+#include "st-theme-image.h"
+
+G_BEGIN_DECLS
+
+typedef struct _StTheme          StTheme;
+typedef struct _StThemeContext   StThemeContext;
+
+typedef struct _StThemeNode      StThemeNode;
+typedef struct _StThemeNodeClass StThemeNodeClass;
+
+#define ST_TYPE_THEME_NODE              (st_theme_node_get_type ())
+#define ST_THEME_NODE(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), ST_TYPE_THEME_NODE, StThemeNode))
+#define ST_THEME_NODE_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass),     ST_TYPE_THEME_NODE, StThemeNodeClass))
+#define ST_IS_THEME_NODE(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), ST_TYPE_THEME_NODE))
+#define ST_IS_THEME_NODE_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass),     ST_TYPE_THEME_NODE))
+#define ST_THEME_NODE_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj),     ST_TYPE_THEME_NODE, StThemeNodeClass))
+
+typedef enum {
+    ST_SIDE_LEFT,
+    ST_SIDE_RIGHT,
+    ST_SIDE_TOP,
+    ST_SIDE_BOTTOM
+} StSide;
+
+/* These are the CSS values; that doesn't mean we have to implement blink... */
+typedef enum {
+    ST_TEXT_DECORATION_UNDERLINE    = 1 << 0,
+    ST_TEXT_DECORATION_OVERLINE     = 1 << 1,
+    ST_TEXT_DECORATION_LINE_THROUGH = 1 << 2,
+    ST_TEXT_DECORATION_BLINK        = 1 << 3
+} StTextDecoration;
+
+GType st_theme_node_get_type (void) G_GNUC_CONST;
+
+/* An element_type of G_TYPE_NONE means this style was created for the stage
+ * actor and matches a selector element name of 'stage'
+ */
+StThemeNode *st_theme_node_new (StThemeContext *context,
+                                StThemeNode    *parent_node,   /* can be null */
+                                StTheme        *theme,         /* can be null */
+                                GType           element_type,
+                                const char     *element_id,
+                                const char     *element_class,
+                                const char     *pseudo_class);
+
+StThemeNode *st_theme_node_get_parent (StThemeNode *node);
+
+StTheme *st_theme_node_get_theme (StThemeNode *node);
+
+GType       st_theme_node_get_element_type  (StThemeNode *node);
+const char *st_theme_node_get_element_id    (StThemeNode *node);
+const char *st_theme_node_get_element_class (StThemeNode *node);
+const char *st_theme_node_get_pseudo_class  (StThemeNode *node);
+
+/* Generic getters ... these are not cached so are less efficient. The other
+ * reason for adding the more specific version is that we can handle the
+ * details of the actual CSS rules, which can be complicated, especially
+ * for fonts
+ */
+gboolean st_theme_node_get_color  (StThemeNode  *node,
+                                   const char   *property_name,
+                                   gboolean      inherit,
+                                   ClutterColor *color);
+
+gboolean st_theme_node_get_double (StThemeNode  *node,
+                                   const char   *property_name,
+                                   gboolean      inherit,
+                                   double       *value);
+
+/* The length here is already resolved to pixels
+ */
+gboolean st_theme_node_get_length (StThemeNode *node,
+                                   const char  *property_name,
+                                   gboolean     inherit,
+                                   gdouble     *length);
+
+/* Specific getters for particular properties: cached
+ */
+void st_theme_node_get_background_color (StThemeNode  *node,
+                                         ClutterColor *color);
+void st_theme_node_get_foreground_color (StThemeNode  *node,
+                                         ClutterColor *color);
+
+const char *st_theme_node_get_background_image (StThemeNode *node);
+
+double st_theme_node_get_border_width (StThemeNode  *node,
+                                       StSide        side);
+void   st_theme_node_get_border_color (StThemeNode  *node,
+                                       StSide        side,
+                                       ClutterColor *color);
+double st_theme_node_get_padding      (StThemeNode  *node,
+                                       StSide        side);
+
+StTextDecoration st_theme_node_get_text_decoration (StThemeNode *node);
+
+/* Font rule processing is pretty complicated, so we just hardcode it
+ * under the standard font/font-family/font-size/etc names. This means
+ * you can't have multiple separate styled fonts for a single item,
+ * but that should be OK.
+ */
+const PangoFontDescription *st_theme_node_get_font (StThemeNode *node);
+
+/* This is the getter for -st-background-image, which is different from
+ * background-image in having provisions for unscaled borders.
+ */
+StThemeImage *st_theme_node_get_background_theme_image (StThemeNode *node);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_NODE_H__ */
diff --git a/src/st/st-theme-private.h b/src/st/st-theme-private.h
new file mode 100644
index 0000000..c2478e4
--- /dev/null
+++ b/src/st/st-theme-private.h
@@ -0,0 +1,22 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __ST_THEME_PRIVATE_H__
+#define __ST_THEME_PRIVATE_H__
+
+#include <libcroco/libcroco.h>
+#include "st-theme.h"
+
+G_BEGIN_DECLS
+
+void _st_theme_get_matched_properties (StTheme         *theme,
+                                       StThemeNode     *node,
+                                       CRDeclaration ***properties,
+                                       int             *n_properties);
+
+/* Resolve an URL from the stylesheet to a filename */
+char *_st_theme_resolve_url (StTheme      *theme,
+                             CRStyleSheet *base_stylesheet,
+                             const char   *url);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_PRIVATE_H__ */
diff --git a/src/st/st-theme.c b/src/st/st-theme.c
new file mode 100644
index 0000000..87faa3b
--- /dev/null
+++ b/src/st/st-theme.c
@@ -0,0 +1,1032 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* This file started as a cut-and-paste of cr-sel-eng.c from libcroco.
+ *
+ * In moving it to hippo-canvas:
+ * - Reformatted and otherwise edited to match our coding style
+ * - Switched from handling xmlNode to handling HippoStyle
+ * - Simplified by removing things that we don't need or that don't
+ *   make sense in our context.
+ * - The code to get a list of matching properties works quite differently;
+ *   we order things in priority order, but we don't actually try to
+ *   coelesce properties with the same name.
+ *
+ * In moving it to GNOME Shell:
+ *  - Renamed again to StTheme
+ *  - Reformatted to match the gnome-st coding style
+ *  - Removed notion of "theme engine" from hippo-canvas
+ *  - pseudo-class matching changed from link enum to strings
+ *  - Some code simplication
+ */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser
+ * General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Copyright (C) 2003-2004 Dodji Seketeli.  All Rights Reserved.
+ */
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "st-theme-node.h"
+#include "st-theme-private.h"
+
+static GObject *st_theme_constructor (GType                  type,
+                                      guint                  n_construct_properties,
+                                      GObjectConstructParam *construct_properties);
+
+static void st_theme_dispose      (GObject      *object);
+static void st_theme_finalize     (GObject      *object);
+static void st_theme_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec);
+static void st_theme_get_property (GObject      *object,
+                                   guint         prop_id,
+                                   GValue       *value,
+                                   GParamSpec   *pspec);
+
+#if 0
+enum
+{
+  LAST_SIGNAL
+};
+
+static int signals[LAST_SIGNAL];
+#endif
+
+struct _StTheme
+{
+  GObject parent;
+
+  char *application_stylesheet;
+  char *default_stylesheet;
+  char *theme_stylesheet;
+
+  GHashTable *stylesheets_by_filename;
+  GHashTable *filenames_by_stylesheet;
+
+  CRCascade *cascade;
+};
+
+struct _StThemeClass
+{
+  GObjectClass parent_class;
+};
+
+enum
+{
+  PROP_0,
+  PROP_APPLICATION_STYLESHEET,
+  PROP_THEME_STYLESHEET,
+  PROP_DEFAULT_STYLESHEET
+};
+
+G_DEFINE_TYPE (StTheme, st_theme, G_TYPE_OBJECT)
+
+/* Quick strcmp.  Test only for == 0 or != 0, not < 0 or > 0.  */
+#define strqcmp(str,lit,lit_len) \
+  (strlen (str) != (lit_len) || memcmp (str, lit, lit_len))
+
+static void st_theme_init (StTheme * theme)
+{
+  theme->stylesheets_by_filename = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                          (GDestroyNotify)g_free, (GDestroyNotify)cr_stylesheet_unref);
+  theme->filenames_by_stylesheet = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+st_theme_class_init (StThemeClass * klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructor = st_theme_constructor;
+  object_class->dispose = st_theme_dispose;
+  object_class->finalize = st_theme_finalize;
+  object_class->set_property = st_theme_set_property;
+  object_class->get_property = st_theme_get_property;
+
+  /**
+   * StTheme:application-stylesheet
+   *
+   * The highest priority stylesheet, representing application-specific
+   * styling; this is associated with the CSS "author" stylesheet.
+   */
+  g_object_class_install_property (object_class,
+				   PROP_APPLICATION_STYLESHEET,
+				   g_param_spec_string ("application-stylesheet",
+                                                        "Application Stylesheet",
+                                                        "Stylesheet with application-specific styling",
+                                                        NULL,
+                                                        G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+  /**
+   * StTheme:theme-stylesheet
+   *
+   * The second priority stylesheet, representing theme-specific styling;
+   * this is associated with the CSS "user" stylesheet.
+   */
+  g_object_class_install_property (object_class,
+				   PROP_THEME_STYLESHEET,
+				   g_param_spec_string ("theme-stylesheet",
+							"Theme Stylesheet",
+							"Stylesheet with theme-specific styling",
+							NULL,
+							G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+  /**
+   * StTheme:default-stylesheet
+   *
+   * The lowest priority stylesheet, representing global default
+   * styling; this is associated with the CSS "user agent" stylesheet.
+   */
+  g_object_class_install_property (object_class,
+				   PROP_DEFAULT_STYLESHEET,
+				   g_param_spec_string ("default-stylesheet",
+							"Default Stylesheet",
+							"Stylesheet with global default styling",
+							NULL,
+							G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+}
+
+static CRStyleSheet *
+parse_stylesheet (const char *filename)
+{
+  enum CRStatus status;
+  CRStyleSheet *stylesheet;
+
+  if (filename == NULL)
+    return NULL;
+
+  status = cr_om_parser_simply_parse_file ((const guchar *) filename,
+					   CR_UTF_8,
+                                           &stylesheet);
+
+  if (status != CR_OK)
+    {
+      g_warning ("Error parsing stylesheet '%s'", filename);
+      return NULL;
+    }
+
+  return stylesheet;
+}
+
+static void
+insert_stylesheet (StTheme      *theme,
+		   const char   *filename,
+                   CRStyleSheet *stylesheet)
+{
+  char *filename_copy;
+
+  if (stylesheet == NULL)
+    return;
+
+  filename_copy = g_strdup(filename);
+  cr_stylesheet_ref (stylesheet);
+
+  g_hash_table_insert (theme->stylesheets_by_filename, filename_copy, stylesheet);
+  g_hash_table_insert (theme->filenames_by_stylesheet, stylesheet, filename_copy);
+}
+
+static GObject *
+st_theme_constructor (GType                  type,
+                      guint                  n_construct_properties,
+                      GObjectConstructParam *construct_properties)
+{
+  GObject *object;
+  StTheme *theme;
+  CRStyleSheet *application_stylesheet;
+  CRStyleSheet *theme_stylesheet;
+  CRStyleSheet *default_stylesheet;
+
+  object = (*G_OBJECT_CLASS (st_theme_parent_class)->constructor) (type,
+								      n_construct_properties,
+								      construct_properties);
+  theme = ST_THEME (object);
+
+  application_stylesheet = parse_stylesheet (theme->application_stylesheet);
+  theme_stylesheet = parse_stylesheet (theme->theme_stylesheet);
+  default_stylesheet = parse_stylesheet (theme->default_stylesheet);
+
+  theme->cascade = cr_cascade_new (application_stylesheet,
+				   theme_stylesheet,
+                                   default_stylesheet);
+
+  if (theme->cascade == NULL)
+    g_error ("Out of memory when creating cascade object");
+
+  insert_stylesheet (theme, theme->application_stylesheet, application_stylesheet);
+  insert_stylesheet (theme, theme->theme_stylesheet, theme_stylesheet);
+  insert_stylesheet (theme, theme->default_stylesheet, default_stylesheet);
+
+  return object;
+}
+
+static void
+st_theme_dispose (GObject * object)
+{
+  /* StTheme *theme = ST_THEME(object); */
+
+  G_OBJECT_CLASS (st_theme_parent_class)->dispose (object);
+}
+
+static void
+st_theme_finalize (GObject * object)
+{
+  StTheme *theme = ST_THEME (object);
+
+  g_hash_table_destroy (theme->stylesheets_by_filename);
+  g_hash_table_destroy (theme->filenames_by_stylesheet);
+
+  g_free (theme->application_stylesheet);
+  g_free (theme->theme_stylesheet);
+  g_free (theme->default_stylesheet);
+
+  if (theme->cascade)
+    {
+      cr_cascade_unref (theme->cascade);
+      theme->cascade = NULL;
+    }
+
+  G_OBJECT_CLASS (st_theme_parent_class)->finalize (object);
+}
+
+static void
+st_theme_set_property (GObject      *object,
+                       guint         prop_id,
+                       const GValue *value,
+                       GParamSpec   *pspec)
+{
+  StTheme *theme = ST_THEME (object);
+
+  switch (prop_id)
+    {
+    case PROP_APPLICATION_STYLESHEET:
+      {
+	const char *path = g_value_get_string (value);
+
+	if (path != theme->application_stylesheet)
+	  {
+	    g_free (theme->application_stylesheet);
+	    theme->application_stylesheet = g_strdup (path);
+	  }
+
+	break;
+      }
+    case PROP_THEME_STYLESHEET:
+      {
+	const char *path = g_value_get_string (value);
+
+	if (path != theme->theme_stylesheet)
+	  {
+	    g_free (theme->theme_stylesheet);
+	    theme->theme_stylesheet = g_strdup (path);
+	  }
+
+	break;
+      }
+    case PROP_DEFAULT_STYLESHEET:
+      {
+	const char *path = g_value_get_string (value);
+
+	if (path != theme->default_stylesheet)
+	  {
+	    g_free (theme->default_stylesheet);
+	    theme->default_stylesheet = g_strdup (path);
+	  }
+
+	break;
+      }
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_theme_get_property (GObject    *object,
+                       guint       prop_id,
+                       GValue     *value,
+                       GParamSpec *pspec)
+{
+  StTheme *theme = ST_THEME (object);
+
+  switch (prop_id)
+    {
+    case PROP_APPLICATION_STYLESHEET:
+      g_value_set_string (value, theme->application_stylesheet);
+      break;
+    case PROP_THEME_STYLESHEET:
+      g_value_set_string (value, theme->theme_stylesheet);
+      break;
+    case PROP_DEFAULT_STYLESHEET:
+      g_value_set_string (value, theme->default_stylesheet);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+/**
+ * st_theme_new:
+ * @application_stylesheet: The highest priority stylesheet, representing application-specific
+ *   styling; this is associated with the CSS "author" stylesheet, may be %NULL
+ * @theme_stylesheet: The second priority stylesheet, representing theme-specific styling ;
+ *   this is associated with the CSS "user" stylesheet, may be %NULL
+ * @default_stylesheet: The lowest priority stylesheet, representing global default styling;
+ *   this is associated with the CSS "user agent" stylesheet, may be %NULL
+ *
+ * Return value: the newly created theme object
+ **/
+StTheme *
+st_theme_new (const char       *application_stylesheet,
+              const char       *theme_stylesheet,
+              const char       *default_stylesheet)
+{
+  StTheme *theme = g_object_new (ST_TYPE_THEME,
+				    "application-stylesheet", application_stylesheet,
+				    "theme-stylesheet", theme_stylesheet,
+				    "default-stylesheet", default_stylesheet,
+				    NULL);
+
+  return theme;
+}
+
+static gboolean
+string_in_list (GString    *stryng,
+                const char *list)
+{
+  const char *cur;
+
+  for (cur = list; *cur;)
+    {
+      while (*cur && cr_utils_is_white_space (*cur))
+        cur++;
+
+      if (strncmp (cur, stryng->str, stryng->len) == 0)
+        {
+          cur +=  stryng->len;
+          if ((!*cur) || cr_utils_is_white_space (*cur))
+            return TRUE;
+        }
+
+      /*  skip to next whitespace character  */
+      while (*cur && !cr_utils_is_white_space (*cur))
+        cur++;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+pseudo_class_add_sel_matches_style (StTheme         *a_this,
+				    CRAdditionalSel *a_add_sel,
+				    StThemeNode     *a_node)
+{
+  const char *node_pseudo_class;
+
+  g_return_val_if_fail (a_this
+			&& a_add_sel
+			&& a_add_sel->content.pseudo
+			&& a_add_sel->content.pseudo->name
+			&& a_add_sel->content.pseudo->name->stryng
+			&& a_add_sel->content.pseudo->name->stryng->str
+			&& a_node, FALSE);
+
+  node_pseudo_class = st_theme_node_get_pseudo_class (a_node);
+
+  if (node_pseudo_class == NULL)
+    return FALSE;
+
+  return string_in_list (a_add_sel->content.pseudo->name->stryng, node_pseudo_class);
+}
+
+/**
+ * param a_add_sel the class additional selector to consider.
+ * param a_node the style node to consider.
+ * return TRUE if the class additional selector matches
+ *the style node given in argument, FALSE otherwise.
+ */
+static gboolean
+class_add_sel_matches_style (CRAdditionalSel *a_add_sel,
+			     StThemeNode     *a_node)
+{
+  const char *element_class;
+
+  g_return_val_if_fail (a_add_sel
+			&& a_add_sel->type == CLASS_ADD_SELECTOR
+			&& a_add_sel->content.class_name
+			&& a_add_sel->content.class_name->stryng
+			&& a_add_sel->content.class_name->stryng->str
+			&& a_node, FALSE);
+
+  element_class = st_theme_node_get_element_class (a_node);
+  if (element_class == NULL)
+    return FALSE;
+
+  return string_in_list (a_add_sel->content.class_name->stryng, element_class);
+}
+
+/**
+ * return TRUE if the additional attribute selector matches
+ *the current style node given in argument, FALSE otherwise.
+ * param a_add_sel the additional attribute selector to consider.
+ * param a_node the style node to consider.
+ */
+static gboolean
+id_add_sel_matches_style (CRAdditionalSel *a_add_sel,
+			  StThemeNode     *a_node)
+{
+  gboolean result = FALSE;
+  const char *id;
+
+  g_return_val_if_fail (a_add_sel
+			&& a_add_sel->type == ID_ADD_SELECTOR
+			&& a_add_sel->content.id_name
+			&& a_add_sel->content.id_name->stryng
+			&& a_add_sel->content.id_name->stryng->str
+			&& a_node, FALSE);
+  g_return_val_if_fail (a_add_sel
+			&& a_add_sel->type == ID_ADD_SELECTOR
+			&& a_node, FALSE);
+
+  id = st_theme_node_get_element_id (a_node);
+
+  if (id != NULL)
+    {
+      if (!strqcmp (id, a_add_sel->content.id_name->stryng->str,
+		    a_add_sel->content.id_name->stryng->len))
+	{
+	  result = TRUE;
+	}
+    }
+
+  return result;
+}
+
+/**
+ *Evaluates if a given additional selector matches an style node.
+ * param a_add_sel the additional selector to consider.
+ * param a_node the style node to consider.
+ * return TRUE is a_add_sel matches a_node, FALSE otherwise.
+ */
+static gboolean
+additional_selector_matches_style (StTheme         *a_this,
+				   CRAdditionalSel *a_add_sel,
+				   StThemeNode     *a_node)
+{
+  CRAdditionalSel *cur_add_sel = NULL;
+
+  g_return_val_if_fail (a_add_sel, FALSE);
+
+  for (cur_add_sel = a_add_sel; cur_add_sel; cur_add_sel = cur_add_sel->next)
+    {
+      switch (cur_add_sel->type)
+        {
+        case NO_ADD_SELECTOR:
+	  return FALSE;
+        case CLASS_ADD_SELECTOR:
+	  if (!class_add_sel_matches_style (cur_add_sel, a_node))
+            return FALSE;
+          break;
+        case ID_ADD_SELECTOR:
+	  if (!id_add_sel_matches_style (cur_add_sel, a_node))
+            return FALSE;
+          break;
+        case ATTRIBUTE_ADD_SELECTOR:
+	  g_warning ("Attribute selectors not supported");
+	  return FALSE;
+        case  PSEUDO_CLASS_ADD_SELECTOR:
+	  if (!pseudo_class_add_sel_matches_style (a_this, cur_add_sel, a_node))
+            return FALSE;
+          break;
+        }
+    }
+
+  return TRUE;
+}
+
+/* This is a workaround for:
+ *  - Python encodes '.' as '+' in type names
+ *  - libcroco doesn't correctly parse the \+ escape in types
+ *  - it's far too ugly to write foo\2b\bar\2bWidget...
+ *
+ * So we consider an element name of foo-bar-Widget to match a type
+ * of foo+bar+Widget
+ */
+static gboolean
+element_name_matches_type_name (const char *element_name,
+				const char *type_name)
+{
+  const char *p = element_name;
+  const char *q = type_name;
+
+  while (*p || *q)
+    {
+      if (*p == '-')
+	{
+	  if (*q != '-' && *q != '+')
+	    return FALSE;
+	}
+      else
+	{
+	  if (*p != *q)
+	    return FALSE;
+	}
+
+      p++;
+      q++;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+element_name_matches_type (const char *element_name,
+                           GType       element_type)
+{
+  /* This is not efficient, but the number of selectors with an element_name
+   * is probably fairly small.
+   */
+  if (element_type == G_TYPE_NONE)
+    {
+      return strcmp (element_name, "stage") == 0;
+    }
+  else
+    {
+      while (TRUE)
+	{
+	  if (element_name_matches_type_name (element_name, g_type_name (element_type)))
+	    return TRUE;
+
+	  if (element_type == G_TYPE_OBJECT)
+	    return FALSE;
+
+	  element_type = g_type_parent (element_type);
+	}
+    }
+}
+
+/**
+ *Evaluate a selector (a simple selectors list) and says
+ *if it matches the style node given in parameter.
+ *The algorithm used here is the following:
+ *Walk the combinator separated list of simple selectors backward, starting
+ *from the end of the list. For each simple selector, looks if
+ *if matches the current style.
+ *
+ * param a_this the selection engine.
+ * param a_sel the simple selection list.
+ * param a_node the style node.
+ * param a_result out parameter. Set to true if the
+ *selector matches the style node, FALSE otherwise.
+ * param a_recurse if set to TRUE, the function will walk to
+ *the next simple selector (after the evaluation of the current one)
+ *and recursively evaluate it. Must be usually set to TRUE unless you
+ *know what you are doing.
+ */
+static enum CRStatus
+sel_matches_style_real (StTheme     *a_this,
+			CRSimpleSel *a_sel,
+			StThemeNode *a_node,
+			gboolean    *a_result,
+			gboolean     a_eval_sel_list_from_end,
+                        gboolean     a_recurse)
+{
+  CRSimpleSel *cur_sel = NULL;
+  StThemeNode *cur_node = NULL;
+  GType cur_type;
+
+  *a_result = FALSE;
+
+  if (a_eval_sel_list_from_end)
+    {
+      /*go and get the last simple selector of the list */
+      for (cur_sel = a_sel; cur_sel && cur_sel->next; cur_sel = cur_sel->next)
+        ;
+    }
+  else
+    {
+      cur_sel = a_sel;
+    }
+
+  cur_node = a_node;
+  cur_type = st_theme_node_get_element_type (cur_node);
+
+  while (cur_sel)
+    {
+      if (((cur_sel->type_mask & TYPE_SELECTOR)
+	   && (cur_sel->name
+	       && cur_sel->name->stryng
+	       && cur_sel->name->stryng->str)
+	   &&
+	   (element_name_matches_type (cur_sel->name->stryng->str, cur_type)))
+	  || (cur_sel->type_mask & UNIVERSAL_SELECTOR))
+	{
+	  /*
+	   *this simple selector
+	   *matches the current style node
+	   *Let's see if the preceding
+	   *simple selectors also match
+	   *their style node counterpart.
+	   */
+	  if (cur_sel->add_sel)
+	    {
+	      if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
+                goto walk_a_step_in_expr;
+	      else
+                goto done;
+	    }
+	  else
+            goto walk_a_step_in_expr;
+	}
+      if (!(cur_sel->type_mask & TYPE_SELECTOR)
+	  && !(cur_sel->type_mask & UNIVERSAL_SELECTOR))
+	{
+	  if (!cur_sel->add_sel)
+            goto done;
+	  if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
+            goto walk_a_step_in_expr;
+	  else
+            goto done;
+	}
+      else
+	{
+	  goto done;
+	}
+
+    walk_a_step_in_expr:
+      if (a_recurse == FALSE)
+	{
+	  *a_result = TRUE;
+	  goto done;
+	}
+
+      /*
+       *here, depending on the combinator of cur_sel
+       *choose the axis of the element tree traversal
+       *and walk one step in the element tree.
+       */
+      if (!cur_sel->prev)
+	break;
+
+      switch (cur_sel->combinator)
+	{
+	case NO_COMBINATOR:
+	  break;
+
+	case COMB_WS:		/*descendant selector */
+	  {
+	    StThemeNode *n = NULL;
+
+	    /*
+	     *walk the element tree upward looking for a parent
+	     *style that matches the preceding selector.
+	     */
+	    for (n = st_theme_node_get_parent (a_node); n; n = st_theme_node_get_parent (n))
+	      {
+		enum CRStatus status;
+		gboolean matches = FALSE;
+
+		status = sel_matches_style_real (a_this, cur_sel->prev, n, &matches, FALSE, TRUE);
+
+		if (status != CR_OK)
+		  goto done;
+
+		if (matches)
+		  {
+		    cur_node = n;
+		    cur_type = st_theme_node_get_element_type (cur_node);
+		    break;
+		  }
+	      }
+
+	    if (!n)
+	      {
+		/*
+		 *didn't find any ancestor that matches
+		 *the previous simple selector.
+		 */
+		goto done;
+	      }
+	    /*
+	     *in this case, the preceding simple sel
+	     *will have been interpreted twice, which
+	     *is a cpu and mem waste ... I need to find
+	     *another way to do this. Anyway, this is
+	     *my first attempt to write this function and
+	     *I am a bit clueless.
+	     */
+	    break;
+	  }
+
+	case COMB_PLUS:
+	  g_warning ("+ combinators are not supported");
+	  goto done;
+
+	case COMB_GT:
+	  cur_node = st_theme_node_get_parent (cur_node);
+	  if (!cur_node)
+	    goto done;
+	  cur_type = st_theme_node_get_element_type (cur_node);
+	  break;
+
+	default:
+	  goto done;
+	}
+
+      cur_sel = cur_sel->prev;
+    }
+
+  /*
+   *if we reached this point, it means the selector matches
+   *the style node.
+   */
+  *a_result = TRUE;
+
+done:
+  return CR_OK;
+}
+
+static void
+add_matched_properties (StTheme      *a_this,
+			CRStyleSheet *a_nodesheet,
+			StThemeNode  *a_node,
+                        GPtrArray    *props)
+{
+  CRStatement *cur_stmt = NULL;
+  CRSelector *sel_list = NULL;
+  CRSelector *cur_sel = NULL;
+  gboolean matches = FALSE;
+  enum CRStatus status = CR_OK;
+
+  /*
+   *walk through the list of statements and,
+   *get the selectors list inside the statements that
+   *contain some, and try to match our style node in these
+   *selectors lists.
+   */
+  for (cur_stmt = a_nodesheet->statements; cur_stmt; cur_stmt = cur_stmt->next)
+    {
+      /*
+       *initialyze the selector list in which we will
+       *really perform the search.
+       */
+      sel_list = NULL;
+
+      /*
+       *get the the damn selector list in
+       *which we have to look
+       */
+      switch (cur_stmt->type)
+	{
+	case RULESET_STMT:
+	  if (cur_stmt->kind.ruleset && cur_stmt->kind.ruleset->sel_list)
+	    {
+	      sel_list = cur_stmt->kind.ruleset->sel_list;
+	    }
+	  break;
+
+	case AT_MEDIA_RULE_STMT:
+	  if (cur_stmt->kind.media_rule
+	      && cur_stmt->kind.media_rule->rulesets
+	      && cur_stmt->kind.media_rule->rulesets->kind.ruleset
+	      && cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list)
+	    {
+	      sel_list = cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list;
+	    }
+	  break;
+
+	case AT_IMPORT_RULE_STMT:
+	  {
+	    CRAtImportRule *import_rule = cur_stmt->kind.import_rule;
+
+	    if (import_rule->sheet == NULL)
+	      {
+		char *filename = NULL;
+
+		if (import_rule->url->stryng && import_rule->url->stryng->str)
+		  filename = _st_theme_resolve_url (a_this,
+                                                    a_nodesheet,
+                                                    import_rule->url->stryng->str);
+
+		if (filename)
+		  import_rule->sheet = parse_stylesheet (filename);
+
+		if (import_rule->sheet)
+		  {
+		    insert_stylesheet (a_this, filename, import_rule->sheet);
+		    /* refcount of stylesheets starts off at zero, so we don't need to unref! */
+		  }
+		else
+                  {
+                    /* Set a marker to avoid repeatedly trying to parse a non-existent or
+                     * broken stylesheet
+                     */
+                    import_rule->sheet = (CRStyleSheet *) - 1;
+                  }
+
+                if (filename)
+                  g_free (filename);
+              }
+
+            if (import_rule->sheet != (CRStyleSheet *) - 1)
+              {
+                add_matched_properties (a_this, import_rule->sheet,
+                                        a_node, props);
+              }
+          }
+          break;
+        default:
+          break;
+        }
+
+      if (!sel_list)
+        continue;
+
+      /*
+       *now, we have a comma separated selector list to look in.
+       *let's walk it and try to match the style node
+       *on each item of the list.
+       */
+      for (cur_sel = sel_list; cur_sel; cur_sel = cur_sel->next)
+        {
+          if (!cur_sel->simple_sel)
+            continue;
+
+          status = sel_matches_style_real (a_this, cur_sel->simple_sel, a_node, &matches, TRUE, TRUE);
+
+          if (status == CR_OK && matches)
+            {
+              CRDeclaration *cur_decl = NULL;
+
+              /* In order to sort the matching properties, we need to compute the
+               * specificity of the selector that actually matched this
+               * element. In a non-thread-safe fashion, we store it in the
+               * ruleset. (Fixing this would mean cut-and-pasting
+               * cr_simple_sel_compute_specificity(), and have no need for
+               * thread-safety anyways.)
+               *
+               * Once we've sorted the properties, the specificity no longer
+               * matters and it can be safely overriden.
+               */
+              cr_simple_sel_compute_specificity (cur_sel->simple_sel);
+
+              cur_stmt->specificity = cur_sel->simple_sel->specificity;
+
+              for (cur_decl = cur_stmt->kind.ruleset->decl_list; cur_decl; cur_decl = cur_decl->next)
+                g_ptr_array_add (props, cur_decl);
+            }
+        }
+    }
+}
+
+#define ORIGIN_AUTHOR_IMPORTANT (ORIGIN_AUTHOR + 1)
+#define ORIGIN_USER_IMPORTANT   (ORIGIN_AUTHOR + 2)
+
+static inline int
+get_origin (const CRDeclaration * decl)
+{
+  enum CRStyleOrigin origin = decl->parent_statement->parent_sheet->origin;
+
+  if (decl->important)
+    {
+      if (origin == ORIGIN_AUTHOR)
+	return ORIGIN_AUTHOR_IMPORTANT;
+      else if (origin == ORIGIN_USER)
+	return ORIGIN_USER_IMPORTANT;
+    }
+
+  return origin;
+}
+
+/* Order of comparison is so that higher priority statements compare after
+ * lower priority statements */
+static int
+compare_declarations (gconstpointer a,
+                      gconstpointer b)
+{
+  /* g_ptr_array_sort() is broooken */
+  CRDeclaration *decl_a = *(CRDeclaration **) a;
+  CRDeclaration *decl_b = *(CRDeclaration **) b;
+
+  int origin_a = get_origin (decl_a);
+  int origin_b = get_origin (decl_b);
+
+  if (origin_a != origin_b)
+    return origin_a - origin_b;
+
+  if (decl_a->parent_statement->specificity != decl_b->parent_statement->specificity)
+    return decl_a->parent_statement->specificity - decl_b->parent_statement->specificity;
+
+  return 0;
+}
+
+void
+_st_theme_get_matched_properties (StTheme        *theme,
+                                  StThemeNode     *node,
+                                  CRDeclaration ***properties,
+                                  int             *n_properties)
+{
+  enum CRStyleOrigin origin = 0;
+  CRStyleSheet *sheet = NULL;
+  GPtrArray *props = g_ptr_array_new ();
+
+  g_return_if_fail (ST_IS_THEME (theme));
+  g_return_if_fail (ST_IS_THEME_NODE (node));
+
+  for (origin = ORIGIN_UA; origin < NB_ORIGINS; origin++)
+    {
+      sheet = cr_cascade_get_sheet (theme->cascade, origin);
+      if (!sheet)
+	continue;
+
+      add_matched_properties (theme, sheet, node, props);
+    }
+
+  /* We count on a stable sort here so that later declarations come
+   * after earlier declarations */
+  g_ptr_array_sort (props, compare_declarations);
+
+  *n_properties = props->len;
+  *properties = (CRDeclaration **) g_ptr_array_free (props, FALSE);
+}
+
+/* Resolve an url from an url() reference in a stylesheet into an absolute
+ * local filename, if possible. The resolution here is distinctly lame and
+ * will fail on many examples.
+ */
+char *
+_st_theme_resolve_url (StTheme      *theme,
+                       CRStyleSheet *base_stylesheet,
+                       const char   *url)
+{
+  const char *base_filename = NULL;
+  char *dirname;
+  char *filename;
+
+  /* Handle absolute file:/ URLs */
+  if (g_str_has_prefix (url, "file:") ||
+      g_str_has_prefix (url, "File:") ||
+      g_str_has_prefix (url, "FILE:"))
+    {
+      GError *error = NULL;
+      char *filename;
+
+      filename = g_filename_from_uri (url, NULL, &error);
+      if (filename == NULL)
+	{
+	  g_warning ("%s", error->message);
+	  g_error_free (error);
+	}
+
+      return NULL;
+    }
+
+  /* Guard against http:/ URLs */
+
+  if (g_str_has_prefix (url, "http:") ||
+      g_str_has_prefix (url, "Http:") ||
+      g_str_has_prefix (url, "HTTP:"))
+    {
+      g_warning ("Http URL '%s' in theme stylesheet is not supported", url);
+      return NULL;
+    }
+
+  /* Assume anything else is a relative URL, and "resolve" it
+   */
+  if (url[0] == '/')
+    return g_strdup (url);
+
+  base_filename = g_hash_table_lookup (theme->filenames_by_stylesheet, base_stylesheet);
+
+  if (base_filename == NULL)
+    {
+      g_warning ("Can't get base to resolve url '%s'", url);
+      return NULL;
+    }
+
+  dirname = g_path_get_dirname (base_filename);
+  filename = g_build_filename (dirname, url, NULL);
+  g_free (dirname);
+
+  return filename;
+}
diff --git a/src/st/st-theme.h b/src/st/st-theme.h
new file mode 100644
index 0000000..a1b9806
--- /dev/null
+++ b/src/st/st-theme.h
@@ -0,0 +1,28 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __ST_THEME_H__
+#define __ST_THEME_H__
+
+#include <glib-object.h>
+
+#include "st-theme-node.h"
+
+G_BEGIN_DECLS
+
+typedef struct _StThemeClass StThemeClass;
+
+#define ST_TYPE_THEME              (st_theme_get_type ())
+#define ST_THEME(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), ST_TYPE_THEME, StTheme))
+#define ST_THEME_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_THEME, StThemeClass))
+#define ST_IS_THEME(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), ST_TYPE_THEME))
+#define ST_IS_THEME_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_THEME))
+#define ST_THEME_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_THEME, StThemeClass))
+
+GType  st_theme_get_type (void) G_GNUC_CONST;
+
+StTheme *st_theme_new (const char *application_stylesheet,
+                       const char *theme_stylesheet,
+                       const char *default_stylesheet);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_H__ */
diff --git a/src/st/test-theme.c b/src/st/test-theme.c
new file mode 100644
index 0000000..555931d
--- /dev/null
+++ b/src/st/test-theme.c
@@ -0,0 +1,292 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <clutter/clutter.h>
+#include "st-theme.h"
+#include "st-theme-context.h"
+#include <math.h>
+#include <string.h>
+
+static StThemeNode *root;
+static StThemeNode *group1;
+static StThemeNode *text1;
+static StThemeNode *text2;
+static StThemeNode *group2;
+static StThemeNode *text3;
+static StThemeNode *text4;
+static StThemeNode *group3;
+static StThemeNode *cairo_texture;
+static gboolean fail;
+
+static const char *test;
+
+static void
+assert_font (StThemeNode *node,
+	     const char  *node_description,
+	     const char  *expected)
+{
+  char *value = pango_font_description_to_string (st_theme_node_get_font (node));
+
+  if (strcmp (expected, value) != 0)
+    {
+      g_print ("%s: %s.font: expected: %s, got: %s\n",
+	       test, node_description, expected, value);
+      fail = TRUE;
+    }
+
+  g_free (value);
+}
+
+static char *
+text_decoration_to_string (StTextDecoration decoration)
+{
+  GString *result = g_string_new (NULL);
+
+  if (decoration & ST_TEXT_DECORATION_UNDERLINE)
+    g_string_append(result, " underline");
+  if (decoration & ST_TEXT_DECORATION_OVERLINE)
+    g_string_append(result, " overline");
+  if (decoration & ST_TEXT_DECORATION_LINE_THROUGH)
+    g_string_append(result, " line_through");
+  if (decoration & ST_TEXT_DECORATION_BLINK)
+    g_string_append(result, " blink");
+
+  if (result->len > 0)
+    g_string_erase (result, 0, 1);
+  else
+    g_string_append(result, "none");
+
+  return g_string_free (result, FALSE);
+}
+
+static void
+assert_text_decoration (StThemeNode     *node,
+			const char      *node_description,
+			StTextDecoration expected)
+{
+  StTextDecoration value = st_theme_node_get_text_decoration (node);
+  if (expected != value)
+    {
+      char *es = text_decoration_to_string (expected);
+      char *vs = text_decoration_to_string (value);
+
+      g_print ("%s: %s.text-decoration: expected: %s, got: %s\n",
+	       test, node_description, es, vs);
+      fail = TRUE;
+
+      g_free (es);
+      g_free (vs);
+    }
+}
+
+static void
+assert_foreground_color (StThemeNode *node,
+			 const char  *node_description,
+			 guint32      expected)
+{
+  ClutterColor color;
+  st_theme_node_get_foreground_color (node, &color);
+  guint32 value = clutter_color_to_pixel (&color);
+
+  if (expected != value)
+    {
+      g_print ("%s: %s.color: expected: #%08x, got: #%08x\n",
+	       test, node_description, expected, value);
+      fail = TRUE;
+    }
+}
+
+static void
+assert_background_color (StThemeNode *node,
+			 const char  *node_description,
+			 guint32      expected)
+{
+  ClutterColor color;
+  st_theme_node_get_background_color (node, &color);
+  guint32 value = clutter_color_to_pixel (&color);
+
+  if (expected != value)
+    {
+      g_print ("%s: %s.background-color: expected: #%08x, got: #%08x\n",
+	       test, node_description, expected, value);
+      fail = TRUE;
+    }
+}
+
+static void
+assert_background_image (StThemeNode *node,
+			 const char  *node_description,
+			 const char  *expected)
+{
+  const char *value = st_theme_node_get_background_image (node);
+  if (expected == NULL)
+    expected = "(null)";
+  if (value == NULL)
+    value = "(null)";
+
+  if (strcmp (expected, value) != 0)
+    {
+      g_print ("%s: %s.background-image: expected: %s, got: %s\n",
+	       test, node_description, expected, value);
+      fail = TRUE;
+    }
+}
+
+#define LENGTH_EPSILON 0.001
+
+static void
+assert_length (const char     *node_description,
+	       const char     *property_description,
+	       double          expected,
+	       double          value)
+{
+  if (fabs (expected - value) > LENGTH_EPSILON)
+    {
+      g_print ("%s %s.%s: expected: %3f, got: %3f\n",
+	       test, node_description, property_description, expected, value);
+      fail = TRUE;
+    }
+}
+
+static void
+test_defaults (void)
+{
+  test = "defaults";
+  /* font comes from context */
+  assert_font (root, "stage", "sans-serif 12");
+  /* black is the default foreground color */
+  assert_foreground_color (root, "stage", 0x00000ff);
+}
+
+static void
+test_lengths (void)
+{
+  test = "lengths";
+  /* 12pt == 16px at 96dpi */
+  assert_length ("group1", "padding-top", 16.,
+		 st_theme_node_get_padding (group1, ST_SIDE_TOP));
+  /* 12px == 12px */
+  assert_length ("group1", "padding-right", 12.,
+		 st_theme_node_get_padding (group1, ST_SIDE_RIGHT));
+  /* 2em == 32px (with a 12pt font) */
+  assert_length ("group1", "padding-bottom", 32.,
+		 st_theme_node_get_padding (group1, ST_SIDE_BOTTOM));
+  /* 1in == 72pt == 96px, at 96dpi */
+  assert_length ("group1", "padding-left", 96.,
+		 st_theme_node_get_padding (group1, ST_SIDE_LEFT));
+}
+
+static void
+test_classes (void)
+{
+  test = "classes";
+  /* .special-text class overrides size and style;
+   * the ClutterTexture.special-text selector doesn't match */
+  assert_font (text1, "text1", "sans-serif Italic 32px");
+}
+
+static void
+test_type_inheritance (void)
+{
+  test = "type_inheritance";
+  /* From ClutterTexture element selector */
+  assert_length ("cairoTexture", "padding-top", 10.,
+		 st_theme_node_get_padding (cairo_texture, ST_SIDE_TOP));
+  /* From ClutterCairoTexture element selector */
+  assert_length ("cairoTexture", "padding-right", 20.,
+		 st_theme_node_get_padding (cairo_texture, ST_SIDE_RIGHT));
+}
+
+static void
+test_adjacent_selector (void)
+{
+  test = "adjacent_selector";
+  /* #group1 > #text1 matches text1 */
+  assert_foreground_color (text1, "text1", 0x00ff00ff);
+  /* stage > #text2 doesn't match text2 */
+  assert_foreground_color (text2, "text2", 0x000000ff);
+}
+
+static void
+test_background (void)
+{
+  test = "background";
+  /* group1 has a background: shortcut property setting color and image */
+  assert_background_color (group1, "group1", 0xff0000ff);
+  assert_background_image (group1, "group1", "st/some-background.png");
+  /* text1 inherits the background image but not the color */
+  assert_background_color (text1,  "text1",  0x00000000);
+  assert_background_image (text1,  "text1",  "st/some-background.png");
+  /* text1 inherits inherits both, but then background: none overrides both */
+  assert_background_color (text2,  "text2",  0x00000000);
+  assert_background_image (text2,  "text2",  NULL);
+  /* background-image property */
+  assert_background_image (group2, "group2", "st/other-background.png");
+}
+
+static void
+test_font (void)
+{
+  test = "font";
+  /* font specified with font: */
+  assert_font (group2, "group2", "serif Italic 12px");
+  /* text3 inherits and overrides individually properties */
+  assert_font (text3,  "text3",  "serif Bold Oblique Small-Caps 24px");
+}
+
+static void
+test_pseudo_class (void)
+{
+  test = "pseudo_class";
+  /* text4 has :visited and :hover pseudo-classes, so should pick up both of these */
+  assert_foreground_color (text4,   "text4",  0x888888ff);
+  assert_text_decoration  (text4,   "text4",  ST_TEXT_DECORATION_UNDERLINE);
+  /* :hover pseudo-class matches, but class doesn't match */
+  assert_text_decoration  (group3,  "group3", 0);
+}
+
+int
+main (int argc, char **argv)
+{
+  StTheme *theme;
+  StThemeContext *context;
+
+  clutter_init (&argc, &argv);
+
+  theme = st_theme_new ("st/test-theme.css",
+                        NULL, NULL);
+
+  context = st_theme_context_new ();
+  st_theme_context_set_theme (context, theme);
+  st_theme_context_set_resolution (context, 96.);
+  st_theme_context_set_font (context,
+				pango_font_description_from_string ("sans-serif 12"));
+
+  root = st_theme_context_get_root_node (context);
+  group1 = st_theme_node_new (context, root, NULL,
+                              CLUTTER_TYPE_GROUP, "group1", NULL, NULL);
+  text1 = st_theme_node_new  (context, group1, NULL,
+                              CLUTTER_TYPE_TEXT, "text1", "special-text", NULL);
+  text2 = st_theme_node_new  (context, group1, NULL,
+                              CLUTTER_TYPE_TEXT, "text2", NULL, NULL);
+  group2 = st_theme_node_new (context, root, NULL,
+                              CLUTTER_TYPE_GROUP, "group2", NULL, NULL);
+  text3 = st_theme_node_new  (context, group2, NULL,
+                              CLUTTER_TYPE_TEXT, "text3", NULL, NULL);
+  text4 = st_theme_node_new  (context, group2, NULL,
+                              CLUTTER_TYPE_TEXT, "text4", NULL, "visited hover");
+  group3 = st_theme_node_new (context, group2, NULL,
+                              CLUTTER_TYPE_GROUP, "group3", NULL, "hover");
+  cairo_texture = st_theme_node_new (context, root, NULL,
+                                     CLUTTER_TYPE_CAIRO_TEXTURE, "cairoTexture", NULL, NULL);
+
+  test_defaults ();
+  test_lengths ();
+  test_classes ();
+  test_type_inheritance ();
+  test_adjacent_selector ();
+  test_background ();
+  test_font ();
+  test_pseudo_class ();
+
+  return fail ? 1 : 0;
+}
diff --git a/src/st/test-theme.css b/src/st/test-theme.css
new file mode 100644
index 0000000..5776e0f
--- /dev/null
+++ b/src/st/test-theme.css
@@ -0,0 +1,68 @@
+stage {
+}
+
+#group1 {
+    padding: 12pt;
+    padding-right: 12px;
+    padding-bottom: 2em;
+    padding-left: 1in;
+
+    background: #ff0000 url('some-background.png');
+}
+
+#text1 {
+    background-image: inherit;
+}
+
+.special-text {
+    font-size: 24pt;
+    font-style: italic;
+}
+
+ClutterTexture.special-text {
+    font-weight: bold;
+}
+
+#text2 {
+    background: inherit;
+    background: none; /* also overrides the color */
+}
+
+#group2 {
+    font: italic 12px serif;
+}
+
+#text3 {
+    font-variant: small-caps;
+    font-weight: bold;
+    font-style: oblique;
+    font-size: 200%;
+}
+
+ClutterTexture {
+    padding: 10px;
+}
+
+ClutterCairoTexture {
+    padding-right: 20px;
+}
+
+#group1 > #text1 {
+    color: #00ff00;
+}
+
+stage > #text2 {
+    color: #ff0000;
+}
+
+#group2 {
+    background-image: url('other-background.png');
+}
+
+ClutterText:hover {
+    text-decoration: underline;
+}
+
+ClutterText:visited {
+    color: #888888;
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]