[pango/serializer: 3/5] Wedge the gtk css parser into pango




commit e6b3cb4c7aae79d1b02cabcc3659fec145d0954e
Author: Matthias Clasen <mclasen redhat com>
Date:   Sat Nov 13 11:01:27 2021 -0500

    Wedge the gtk css parser into pango
    
    This is a brute-force job, just to see how far I get.

 pango/css/gtkcss.h                   |   40 +
 pango/css/gtkcssdataurl.c            |  174 ++++
 pango/css/gtkcssdataurlprivate.h     |   35 +
 pango/css/gtkcssenums.h              |   72 ++
 pango/css/gtkcssenumtypes.c.template |   38 +
 pango/css/gtkcssenumtypes.h.template |   24 +
 pango/css/gtkcsserror.c              |   35 +
 pango/css/gtkcsserror.h              |   48 ++
 pango/css/gtkcsslocation.c           |   72 ++
 pango/css/gtkcsslocation.h           |   43 +
 pango/css/gtkcsslocationprivate.h    |   39 +
 pango/css/gtkcssparser.c             | 1108 +++++++++++++++++++++++++
 pango/css/gtkcssparserprivate.h      |  155 ++++
 pango/css/gtkcsssection.c            |  256 ++++++
 pango/css/gtkcsssection.h            |   59 ++
 pango/css/gtkcssserializer.c         |   72 ++
 pango/css/gtkcssserializerprivate.h  |   34 +
 pango/css/gtkcsstokenizer.c          | 1487 ++++++++++++++++++++++++++++++++++
 pango/css/gtkcsstokenizerprivate.h   |  141 ++++
 pango/css/meson.build                |   54 ++
 pango/meson.build                    |   34 +-
 21 files changed, 4019 insertions(+), 1 deletion(-)
---
diff --git a/pango/css/gtkcss.h b/pango/css/gtkcss.h
new file mode 100644
index 00000000..71ebc2be
--- /dev/null
+++ b/pango/css/gtkcss.h
@@ -0,0 +1,40 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __GTK_CSS_H__
+#define __GTK_CSS_H__
+
+#define __GTK_CSS_H_INSIDE__
+
+#include <glib.h>
+
+#include <pango/css/gtkcssenums.h>
+#include <pango/css/gtkcssenumtypes.h>
+#include <pango/css/gtkcsserror.h>
+#include <pango/css/gtkcsslocation.h>
+#include <pango/css/gtkcsssection.h>
+
+#undef __GTK_CSS_H_INSIDE__
+
+#endif /* __GTK_CSS_H__ */
diff --git a/pango/css/gtkcssdataurl.c b/pango/css/gtkcssdataurl.c
new file mode 100644
index 00000000..53b1ed93
--- /dev/null
+++ b/pango/css/gtkcssdataurl.c
@@ -0,0 +1,174 @@
+/* GStreamer data:// uri source element
+ * Copyright (C) 2009 Igalia S.L
+ * Copyright (C) 2009 Sebastian Dröge <sebastian droege collabora co uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/*<private>
+ * # Data URLs
+ *
+ * These function allow encoding and decoding of data: URLs, see
+ * [RFC 2397](http://tools.ietf.org/html/rfc2397) for more information.
+ */
+
+#include "config.h"
+
+#include "gtkcssdataurlprivate.h"
+
+#include <string.h>
+
+#define _(x) x
+
+/*<private>
+ * gtk_css_data_url_parse:
+ * @url: the URL to parse
+ * @out_mimetype: (out nullable optional): Return location to set the contained
+ *   mime type to. If no mime type was specified, this value is set to %NULL.
+ * @error: error location
+ *
+ * Decodes a data URL according to RFC2397 and returns the decoded data.
+ *
+ * Returns: a new `GBytes` with the decoded data
+ */
+GBytes *
+gtk_css_data_url_parse (const char  *url,
+                        char       **out_mimetype,
+                        GError     **error)
+{
+  char *mimetype = NULL;
+  const char *parameters_start;
+  const char *data_start;
+  GBytes *bytes;
+  gboolean base64 = FALSE;
+  char *charset = NULL;
+  gpointer bdata;
+  gsize bsize;
+
+  /* url must be an URI as defined in RFC 2397
+   * data:[<mediatype>][;base64],<data>
+   */
+  if (g_ascii_strncasecmp ("data:", url, 5) != 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_FILENAME,
+                   _("Not a data: URL"));
+      return NULL;
+    }
+
+  url += 5;
+
+  parameters_start = strchr (url, ';');
+  data_start = strchr (url, ',');
+  if (data_start == NULL)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_FILENAME,
+                   _("Malformed data: URL"));
+      return NULL;
+    }
+  if (parameters_start > data_start)
+    parameters_start = NULL;
+
+  if (data_start != url && parameters_start != url)
+    {
+      mimetype = g_strndup (url,
+                            (parameters_start ? parameters_start
+                                              : data_start) - url);
+    }
+  else
+    {
+      mimetype = NULL;
+    }
+
+  if (parameters_start != NULL)
+    {
+      char *parameters_str;
+      char **parameters;
+      guint i;
+
+      parameters_str = g_strndup (parameters_start + 1, data_start - parameters_start - 1);
+      parameters = g_strsplit (parameters_str, ";", -1);
+
+      for (i = 0; parameters[i] != NULL; i++)
+        {
+          if (g_ascii_strcasecmp ("base64", parameters[i]) == 0)
+            {
+              base64 = TRUE;
+            }
+          else if (g_ascii_strncasecmp ("charset=", parameters[i], 8) == 0)
+            {
+              g_free (charset);
+              charset = g_strdup (parameters[i] + 8);
+            }
+        }
+    g_free (parameters_str);
+    g_strfreev (parameters);
+  }
+
+  /* Skip comma */
+  data_start += 1;
+  if (base64)
+    {
+      bdata = g_base64_decode (data_start, &bsize);
+    }
+  else
+    {
+      /* URI encoded, i.e. "percent" encoding */
+      /* XXX: This doesn't allow nul bytes */
+      bdata = g_uri_unescape_string (data_start, NULL);
+      if (bdata == NULL)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_FILENAME,
+                       _("Could not unescape string"));
+          g_free (mimetype);
+          return NULL;
+        }
+      bsize = strlen (bdata);
+    }
+
+  /* Convert to UTF8 */
+  if ((mimetype == NULL || g_ascii_strcasecmp ("text/plain", mimetype) == 0) &&
+      charset && g_ascii_strcasecmp ("US-ASCII", charset) != 0
+      && g_ascii_strcasecmp ("UTF-8", charset) != 0)
+    {
+      gsize read;
+      gsize written;
+      gpointer data;
+
+      data = g_convert_with_fallback (bdata, bsize,
+                                      "UTF-8", charset, 
+                                      (char *) "*",
+                                      &read, &written, NULL);
+      g_free (bdata);
+
+      bdata = data;
+      bsize = written;
+    }
+  bytes = g_bytes_new_take (bdata, bsize);
+
+  g_free (charset);
+  if (out_mimetype)
+    *out_mimetype = mimetype;
+  else
+    g_free (mimetype);
+
+  return bytes;
+}
diff --git a/pango/css/gtkcssdataurlprivate.h b/pango/css/gtkcssdataurlprivate.h
new file mode 100644
index 00000000..5d0d6a85
--- /dev/null
+++ b/pango/css/gtkcssdataurlprivate.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#ifndef __GTK_CSS_DATA_URL_PRIVATE_H__
+#define __GTK_CSS_DATA_URL_PRIVATE_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+GBytes *                gtk_css_data_url_parse                  (const char             *url,
+                                                                 char                  **out_mimetype,
+                                                                 GError                **error);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_DATA_URL_PRIVATE_H__ */
+
diff --git a/pango/css/gtkcssenums.h b/pango/css/gtkcssenums.h
new file mode 100644
index 00000000..b7f965f8
--- /dev/null
+++ b/pango/css/gtkcssenums.h
@@ -0,0 +1,72 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __GTK_CSS_ENUMS_H__
+#define __GTK_CSS_ENUMS_H__
+
+#include <glib.h>
+
+/**
+ * GtkCssParserError:
+ * @GTK_CSS_PARSER_ERROR_FAILED: Unknown failure.
+ * @GTK_CSS_PARSER_ERROR_SYNTAX: The given text does not form valid syntax
+ * @GTK_CSS_PARSER_ERROR_IMPORT: Failed to import a resource
+ * @GTK_CSS_PARSER_ERROR_NAME: The given name has not been defined
+ * @GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE: The given value is not correct
+ *
+ * Errors that can occur while parsing CSS.
+ *
+ * These errors are unexpected and will cause parts of the given CSS
+ * to be ignored.
+ */
+typedef enum
+{
+  GTK_CSS_PARSER_ERROR_FAILED,
+  GTK_CSS_PARSER_ERROR_SYNTAX,
+  GTK_CSS_PARSER_ERROR_IMPORT,
+  GTK_CSS_PARSER_ERROR_NAME,
+  GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE
+} GtkCssParserError;
+
+/**
+ * GtkCssParserWarning:
+ * @GTK_CSS_PARSER_WARNING_DEPRECATED: The given construct is
+ *   deprecated and will be removed in a future version
+ * @GTK_CSS_PARSER_WARNING_SYNTAX: A syntax construct was used
+ *   that should be avoided
+ * @GTK_CSS_PARSER_WARNING_UNIMPLEMENTED: A feature is not implemented
+ *
+ * Warnings that can occur while parsing CSS.
+ *
+ * Unlike `GtkCssParserError`s, warnings do not cause the parser to
+ * skip any input, but they indicate issues that should be fixed.
+ */
+typedef enum
+{
+  GTK_CSS_PARSER_WARNING_DEPRECATED,
+  GTK_CSS_PARSER_WARNING_SYNTAX,
+  GTK_CSS_PARSER_WARNING_UNIMPLEMENTED
+} GtkCssParserWarning;
+
+#endif /* __GTK_CSS_ENUMS_H__ */
diff --git a/pango/css/gtkcssenumtypes.c.template b/pango/css/gtkcssenumtypes.c.template
new file mode 100644
index 00000000..cb4c9ac2
--- /dev/null
+++ b/pango/css/gtkcssenumtypes.c.template
@@ -0,0 +1,38 @@
+/*** BEGIN file-header ***/
+#include "config.h"
+#include "gtkcssenumtypes.h"
+#include <gtkcss.h>
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@basename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+  static gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+        { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+/*** END value-tail ***/
diff --git a/pango/css/gtkcssenumtypes.h.template b/pango/css/gtkcssenumtypes.h.template
new file mode 100644
index 00000000..6ae067f1
--- /dev/null
+++ b/pango/css/gtkcssenumtypes.h.template
@@ -0,0 +1,24 @@
+/*** BEGIN file-header ***/
+#ifndef __GTK_CSS_ENUM_TYPES_H__
+#define __GTK_CSS_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@basename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* __GTK_CSS_ENUM_TYPES_H__ */
+/*** END file-tail ***/
diff --git a/pango/css/gtkcsserror.c b/pango/css/gtkcsserror.c
new file mode 100644
index 00000000..82428cdf
--- /dev/null
+++ b/pango/css/gtkcsserror.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "gtkcsserror.h"
+
+GQuark
+gtk_css_parser_error_quark (void)
+{
+  return g_quark_from_static_string ("gtk-css-parser-error-quark");
+}
+
+GQuark
+gtk_css_parser_warning_quark (void)
+{
+  return g_quark_from_static_string ("gtk-css-parser-warning-quark");
+}
+
diff --git a/pango/css/gtkcsserror.h b/pango/css/gtkcsserror.h
new file mode 100644
index 00000000..0897d2a7
--- /dev/null
+++ b/pango/css/gtkcsserror.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#ifndef __GTK_CSS_ERROR_H__
+#define __GTK_CSS_ERROR_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GTK_CSS_PARSER_ERROR:
+ *
+ * Domain for `GtkCssParser` errors.
+ */
+#define GTK_CSS_PARSER_ERROR (gtk_css_parser_error_quark ())
+
+GQuark gtk_css_parser_error_quark (void);
+
+/**
+ * GTK_CSS_PARSER_WARNING:
+ *
+ * Domain for `GtkCssParser` warnings.
+ */
+#define GTK_CSS_PARSER_WARNING (gtk_css_parser_warning_quark ())
+
+GQuark gtk_css_parser_warning_quark (void);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_ERROR_H__ */
diff --git a/pango/css/gtkcsslocation.c b/pango/css/gtkcsslocation.c
new file mode 100644
index 00000000..41725ad2
--- /dev/null
+++ b/pango/css/gtkcsslocation.c
@@ -0,0 +1,72 @@
+/* GSK - The GIMP Toolkit
+ * Copyright (C) 2019 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkcsslocationprivate.h"
+
+/**
+ * GtkCssLocation:
+ * @bytes: number of bytes parsed since the beginning
+ * @chars: number of characters parsed since the beginning
+ * @lines: number of full lines that have been parsed. If you want to
+ *   display this as a line number, you need to add 1 to this.
+ * @line_bytes: Number of bytes parsed since the last line break
+ * @line_chars: Number of characters parsed since the last line break
+ *
+ * Represents a location in a file or other source of data parsed
+ * by the CSS engine.
+ *
+ * The @bytes and @line_bytes offsets are meant to be used to
+ * programmatically match data. The @lines and @line_chars offsets
+ * can be used for printing the location in a file.
+ *
+ * Note that the @lines parameter starts from 0 and is increased
+ * whenever a CSS line break is encountered. (CSS defines the C character
+ * sequences "\r\n", "\r", "\n" and "\f" as newlines.)
+ * If your document uses different rules for line breaking, you might want
+ * run into problems here.
+ */
+
+void
+gtk_css_location_init (GtkCssLocation *location)
+{
+  memset (location, 0, sizeof (GtkCssLocation));
+}
+
+void
+gtk_css_location_advance (GtkCssLocation *location,
+                          gsize           bytes,
+                          gsize           chars)
+{
+  location->bytes += bytes;
+  location->chars += chars;
+  location->line_bytes += bytes;
+  location->line_chars += chars;
+}
+
+void
+gtk_css_location_advance_newline (GtkCssLocation *location,
+                                  gboolean        is_windows)
+{
+  gtk_css_location_advance (location, is_windows ? 2 : 1, is_windows ? 2 : 1);
+
+  location->lines++;
+  location->line_bytes = 0;
+  location->line_chars = 0;
+}
+
diff --git a/pango/css/gtkcsslocation.h b/pango/css/gtkcsslocation.h
new file mode 100644
index 00000000..7fc4559f
--- /dev/null
+++ b/pango/css/gtkcsslocation.h
@@ -0,0 +1,43 @@
+/* GSK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_CSS_LOCATION_H__
+#define __GTK_CSS_LOCATION_H__
+
+#if !defined (__GTK_CSS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/css/gtkcss.h> can be included directly."
+#endif
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkCssLocation GtkCssLocation;
+
+struct _GtkCssLocation
+{
+  gsize                  bytes;
+  gsize                  chars;
+  gsize                  lines;
+  gsize                  line_bytes;
+  gsize                  line_chars;
+};
+
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_LOCATION_H__ */
diff --git a/pango/css/gtkcsslocationprivate.h b/pango/css/gtkcsslocationprivate.h
new file mode 100644
index 00000000..46ed3c80
--- /dev/null
+++ b/pango/css/gtkcsslocationprivate.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#ifndef __GTK_CSS_LOCATION_PRIVATE_H__
+#define __GTK_CSS_LOCATION_PRIVATE_H__
+
+#include "gtkcsslocation.h"
+
+G_BEGIN_DECLS
+
+void                    gtk_css_location_init                   (GtkCssLocation         *location);
+
+void                    gtk_css_location_advance                (GtkCssLocation         *location,
+                                                                 gsize                   bytes,
+                                                                 gsize                   chars);
+void                    gtk_css_location_advance_newline        (GtkCssLocation         *location,
+                                                                 gboolean                is_windows);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_LOCATION_PRIVATE_H__ */
+
diff --git a/pango/css/gtkcssparser.c b/pango/css/gtkcssparser.c
new file mode 100644
index 00000000..7668bfc7
--- /dev/null
+++ b/pango/css/gtkcssparser.c
@@ -0,0 +1,1108 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#include "config.h"
+
+#include "gtkcssparserprivate.h"
+
+#include "gtkcssenums.h"
+#include "gtkcsserror.h"
+#include "gtkcsslocationprivate.h"
+
+typedef struct _GtkCssParserBlock GtkCssParserBlock;
+
+struct _GtkCssParser
+{
+  volatile int ref_count;
+
+  GtkCssTokenizer *tokenizer;
+  GFile *file;
+  GFile *directory;
+  GtkCssParserErrorFunc error_func;
+  gpointer user_data;
+  GDestroyNotify user_destroy;
+
+  GArray *blocks;
+  GtkCssLocation location;
+  GtkCssToken token;
+};
+
+struct _GtkCssParserBlock
+{
+  GtkCssLocation start_location;
+  GtkCssTokenType end_token;
+  GtkCssTokenType inherited_end_token;
+  GtkCssTokenType alternative_token;
+};
+
+static GtkCssParser *
+gtk_css_parser_new (GtkCssTokenizer       *tokenizer,
+                    GFile                 *file,
+                    GtkCssParserErrorFunc  error_func,
+                    gpointer               user_data,
+                    GDestroyNotify         user_destroy)
+{
+  GtkCssParser *self;
+
+  self = g_slice_new0 (GtkCssParser);
+
+  self->ref_count = 1;
+  self->tokenizer = gtk_css_tokenizer_ref (tokenizer);
+  if (file)
+    {
+      self->file = g_object_ref (file);
+      self->directory = g_file_get_parent (file);
+    }
+
+  self->error_func = error_func;
+  self->user_data = user_data;
+  self->user_destroy = user_destroy;
+  self->blocks = g_array_new (FALSE, FALSE, sizeof (GtkCssParserBlock));
+
+  return self;
+}
+
+GtkCssParser *
+gtk_css_parser_new_for_file (GFile                 *file,
+                             GtkCssParserErrorFunc  error_func,
+                             gpointer               user_data,
+                             GDestroyNotify         user_destroy,
+                             GError               **error)
+{
+  GBytes *bytes;
+  GtkCssParser *result;
+
+  bytes = g_file_load_bytes (file, NULL, NULL, error);
+  if (bytes == NULL)
+    return NULL;
+
+  result = gtk_css_parser_new_for_bytes (bytes, file, error_func, user_data, user_destroy);
+
+  g_bytes_unref (bytes);
+
+  return result;
+}
+
+GtkCssParser *
+gtk_css_parser_new_for_bytes (GBytes                *bytes,
+                              GFile                 *file,
+                              GtkCssParserErrorFunc  error_func,
+                              gpointer               user_data,
+                              GDestroyNotify         user_destroy)
+{
+  GtkCssTokenizer *tokenizer;
+  GtkCssParser *result;
+  
+  tokenizer = gtk_css_tokenizer_new (bytes);
+  result = gtk_css_parser_new (tokenizer, file, error_func, user_data, user_destroy);
+  gtk_css_tokenizer_unref (tokenizer);
+
+  return result;
+}
+
+static void
+gtk_css_parser_finalize (GtkCssParser *self)
+{
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+
+  g_clear_pointer (&self->tokenizer, gtk_css_tokenizer_unref);
+  g_clear_object (&self->file);
+  g_clear_object (&self->directory);
+  if (self->blocks->len)
+    g_critical ("Finalizing CSS parser with %u remaining blocks", self->blocks->len);
+  g_array_free (self->blocks, TRUE);
+
+  g_slice_free (GtkCssParser, self);
+}
+
+GtkCssParser *
+gtk_css_parser_ref (GtkCssParser *self)
+{
+  g_atomic_int_inc (&self->ref_count);
+
+  return self;
+}
+
+void
+gtk_css_parser_unref (GtkCssParser *self)
+{
+  if (g_atomic_int_dec_and_test (&self->ref_count))
+    gtk_css_parser_finalize (self);
+}
+
+/**
+ * gtk_css_parser_get_file:
+ * @self: a `GtkCssParser`
+ *
+ * Gets the file being parsed. If no file is associated with @self -
+ * for example when raw data is parsed - %NULL is returned.
+ *
+ * Returns: (nullable) (transfer none): The file being parsed
+ */
+GFile *
+gtk_css_parser_get_file (GtkCssParser *self)
+{
+  return self->file;
+}
+
+/**
+ * gtk_css_parser_resolve_url:
+ * @self: a `GtkCssParser`
+ * @url: the URL to resolve
+ *
+ * Resolves a given URL against the parser's location.
+ *
+ * Returns: (nullable) (transfer full): a new `GFile` for the
+ *   resolved URL
+ */
+GFile *
+gtk_css_parser_resolve_url (GtkCssParser *self,
+                            const char   *url)
+{
+  char *scheme;
+
+  scheme = g_uri_parse_scheme (url);
+  if (scheme != NULL)
+    {
+      GFile *file = g_file_new_for_uri (url);
+      g_free (scheme);
+      return file;
+    }
+  g_free (scheme);
+
+  if (self->directory == NULL)
+    return NULL;
+
+  return g_file_resolve_relative_path (self->directory, url);
+}
+
+/**
+ * gtk_css_parser_get_start_location:
+ * @self: a `GtkCssParser`
+ *
+ * Queries the location of the current token.
+ *
+ * This function will return the location of the start of the
+ * current token. In the case a token has been consumed, but no
+ * new token has been queried yet via gtk_css_parser_peek_token()
+ * or gtk_css_parser_get_token(), the previous token's start
+ * location will be returned.
+ *
+ * This function may return the same location as
+ * gtk_css_parser_get_end_location() - in particular at the
+ * beginning and end of the document.
+ *
+ * Returns: the start location
+ **/
+const GtkCssLocation *
+gtk_css_parser_get_start_location (GtkCssParser *self)
+{
+  return &self->location;
+}
+
+/**
+ * gtk_css_parser_get_end_location:
+ * @self: a `GtkCssParser`
+ * @out_location: (caller-allocates) Place to store the location
+ *
+ * Queries the location of the current token.
+ *
+ * This function will return the location of the end of the
+ * current token. In the case a token has been consumed, but no
+ * new token has been queried yet via gtk_css_parser_peek_token()
+ * or gtk_css_parser_get_token(), the previous token's end location
+ * will be returned.
+ *
+ * This function may return the same location as
+ * gtk_css_parser_get_start_location() - in particular at the
+ * beginning and end of the document.
+ *
+ * Returns: the end location
+ **/
+const GtkCssLocation *
+gtk_css_parser_get_end_location (GtkCssParser *self)
+{
+  return gtk_css_tokenizer_get_location (self->tokenizer);
+}
+
+/**
+ * gtk_css_parser_get_block_location:
+ * @self: a `GtkCssParser`
+ *
+ * Queries the start location of the token that started the current
+ * block that is being parsed.
+ *
+ * If no block is currently parsed, the beginning of the document
+ * is returned.
+ *
+ * Returns: The start location of the current block
+ */
+const GtkCssLocation *
+gtk_css_parser_get_block_location (GtkCssParser *self)
+{
+  const GtkCssParserBlock *block;
+
+  if (self->blocks->len == 0)
+    {
+      static const GtkCssLocation start_of_document = { 0, };
+      return &start_of_document;
+    }
+
+  block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
+  return &block->start_location;
+}
+
+static void
+gtk_css_parser_ensure_token (GtkCssParser *self)
+{
+  GError *error = NULL;
+
+  if (!gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
+    return;
+
+  self->location = *gtk_css_tokenizer_get_location (self->tokenizer);
+  if (!gtk_css_tokenizer_read_token (self->tokenizer, &self->token, &error))
+    {
+      /* We ignore the error here, because the resulting token will
+       * likely already trigger an error in the parsing code and
+       * duplicate errors are rather useless.
+       */
+      g_clear_error (&error);
+    }
+}
+
+const GtkCssToken *
+gtk_css_parser_peek_token (GtkCssParser *self)
+{
+  static const GtkCssToken eof_token = { GTK_CSS_TOKEN_EOF, };
+
+  gtk_css_parser_ensure_token (self);
+
+  if (self->blocks->len)
+    {
+      const GtkCssParserBlock *block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 
1);
+      if (gtk_css_token_is (&self->token, block->end_token) ||
+          gtk_css_token_is (&self->token, block->inherited_end_token) ||
+          gtk_css_token_is (&self->token, block->alternative_token))
+        return &eof_token;
+    }
+
+  return &self->token;
+}
+
+const GtkCssToken *
+gtk_css_parser_get_token (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+
+  for (token = gtk_css_parser_peek_token (self);
+       gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT) ||
+       gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE);
+       token = gtk_css_parser_peek_token (self))
+    {
+      gtk_css_parser_consume_token (self);
+    }
+
+  return token;
+}
+
+void
+gtk_css_parser_consume_token (GtkCssParser *self)
+{
+  gtk_css_parser_ensure_token (self);
+
+  /* unpreserved tokens MUST be consumed via start_block() */
+  g_assert (gtk_css_token_is_preserved (&self->token, NULL));
+
+  /* Don't consume any tokens at the end of a block */
+  if (!gtk_css_token_is (gtk_css_parser_peek_token (self), GTK_CSS_TOKEN_EOF))
+    gtk_css_token_clear (&self->token);
+}
+
+void
+gtk_css_parser_start_block (GtkCssParser *self)
+{
+  GtkCssParserBlock block;
+
+  gtk_css_parser_ensure_token (self);
+
+  if (gtk_css_token_is_preserved (&self->token, &block.end_token))
+    {
+      g_critical ("gtk_css_parser_start_block() may only be called for non-preserved tokens");
+      return;
+    }
+
+  block.inherited_end_token = GTK_CSS_TOKEN_EOF;
+  block.alternative_token = GTK_CSS_TOKEN_EOF;
+  block.start_location = self->location;
+  g_array_append_val (self->blocks, block);
+
+  gtk_css_token_clear (&self->token);
+}
+
+void
+gtk_css_parser_start_semicolon_block (GtkCssParser    *self,
+                                      GtkCssTokenType  alternative_token)
+{
+  GtkCssParserBlock block;
+
+  block.end_token = GTK_CSS_TOKEN_SEMICOLON;
+  if (self->blocks->len)
+    block.inherited_end_token = g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 
1).end_token;
+  else
+    block.inherited_end_token = GTK_CSS_TOKEN_EOF;
+  block.alternative_token = alternative_token;
+  block.start_location = self->location;
+  g_array_append_val (self->blocks, block);
+}
+
+void
+gtk_css_parser_end_block_prelude (GtkCssParser *self)
+{
+  GtkCssParserBlock *block;
+
+  g_return_if_fail (self->blocks->len > 0);
+
+  block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
+
+  if (block->alternative_token == GTK_CSS_TOKEN_EOF)
+    return;
+
+  gtk_css_parser_skip_until (self, GTK_CSS_TOKEN_EOF);
+
+  if (gtk_css_token_is (&self->token, block->alternative_token))
+    {
+      if (gtk_css_token_is_preserved (&self->token, &block->end_token))
+        {
+          g_critical ("alternative token is not preserved");
+          return;
+        }
+      block->alternative_token = GTK_CSS_TOKEN_EOF;
+      block->inherited_end_token = GTK_CSS_TOKEN_EOF;
+      gtk_css_token_clear (&self->token);
+    }
+}
+
+void
+gtk_css_parser_end_block (GtkCssParser *self)
+{
+  GtkCssParserBlock *block;
+
+  g_return_if_fail (self->blocks->len > 0);
+
+  gtk_css_parser_skip_until (self, GTK_CSS_TOKEN_EOF);
+
+  block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
+
+  if (gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
+    {
+      gtk_css_parser_warn (self,
+                           GTK_CSS_PARSER_WARNING_SYNTAX,
+                           gtk_css_parser_get_block_location (self),
+                           gtk_css_parser_get_start_location (self),
+                           "Unterminated block at end of document");
+      g_array_set_size (self->blocks, self->blocks->len - 1);
+    }
+  else if (gtk_css_token_is (&self->token, block->inherited_end_token))
+    {
+      g_assert (block->end_token == GTK_CSS_TOKEN_SEMICOLON);
+      gtk_css_parser_warn (self,
+                           GTK_CSS_PARSER_WARNING_SYNTAX,
+                           gtk_css_parser_get_block_location (self),
+                           gtk_css_parser_get_start_location (self),
+                           "Expected ';' at end of block");
+      g_array_set_size (self->blocks, self->blocks->len - 1);
+    }
+  else
+    {
+      g_array_set_size (self->blocks, self->blocks->len - 1);
+      if (gtk_css_token_is_preserved (&self->token, NULL))
+        {
+          gtk_css_token_clear (&self->token);
+        }
+      else
+        {
+          gtk_css_parser_start_block (self);
+          gtk_css_parser_end_block (self);
+        }
+    }
+}
+
+/*
+ * gtk_css_parser_skip:
+ * @self: a `GtkCssParser`
+ *
+ * Skips a component value.
+ *
+ * This means that if the token is a preserved token, only
+ * this token will be skipped. If the token starts a block,
+ * the whole block will be skipped.
+ **/
+void
+gtk_css_parser_skip (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+  
+  token = gtk_css_parser_get_token (self);
+  if (gtk_css_token_is_preserved (token, NULL))
+    {
+      gtk_css_parser_consume_token (self);
+    }
+  else
+    {
+      gtk_css_parser_start_block (self);
+      gtk_css_parser_end_block (self);
+    }
+}
+
+/*
+ * gtk_css_parser_skip_until:
+ * @self: a `GtkCssParser`
+ * @token_type: type of token to skip to
+ *
+ * Repeatedly skips a token until a certain type is reached.
+ * After this called, gtk_css_parser_get_token() will either
+ * return a token of this type or the eof token.
+ *
+ * This function is useful for resyncing a parser after encountering
+ * an error.
+ *
+ * If you want to skip until the end, use %GSK_TOKEN_TYPE_EOF
+ * as the token type.
+ **/
+void
+gtk_css_parser_skip_until (GtkCssParser    *self,
+                           GtkCssTokenType  token_type)
+{
+  const GtkCssToken *token;
+  
+  for (token = gtk_css_parser_get_token (self);
+       !gtk_css_token_is (token, token_type) &&
+       !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
+       token = gtk_css_parser_get_token (self))
+    {
+      gtk_css_parser_skip (self);
+    }
+}
+
+void
+gtk_css_parser_emit_error (GtkCssParser         *self,
+                           const GtkCssLocation *start,
+                           const GtkCssLocation *end,
+                           const GError         *error)
+{
+  if (self->error_func)
+    self->error_func (self, start, end, error, self->user_data);
+}
+
+void
+gtk_css_parser_error (GtkCssParser         *self,
+                      GtkCssParserError     code,
+                      const GtkCssLocation *start,
+                      const GtkCssLocation *end,
+                      const char           *format,
+                      ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+                              code,
+                              format, args);
+  gtk_css_parser_emit_error (self, start, end, error);
+  g_error_free (error);
+  va_end (args);
+}
+
+void
+gtk_css_parser_error_syntax (GtkCssParser *self,
+                             const char   *format,
+                             ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+                              GTK_CSS_PARSER_ERROR_SYNTAX,
+                              format, args);
+  gtk_css_parser_emit_error (self,
+                             gtk_css_parser_get_start_location (self),
+                             gtk_css_parser_get_end_location (self),
+                             error);
+  g_error_free (error);
+  va_end (args);
+}
+
+void
+gtk_css_parser_error_value (GtkCssParser *self,
+                            const char   *format,
+                            ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+                              GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
+                              format, args);
+  gtk_css_parser_emit_error (self,
+                             gtk_css_parser_get_start_location (self),
+                             gtk_css_parser_get_end_location (self),
+                             error);
+  g_error_free (error);
+  va_end (args);
+}
+
+void
+gtk_css_parser_error_import (GtkCssParser *self,
+                             const char   *format,
+                             ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+                              GTK_CSS_PARSER_ERROR_IMPORT,
+                              format, args);
+  gtk_css_parser_emit_error (self,
+                             gtk_css_parser_get_start_location (self),
+                             gtk_css_parser_get_end_location (self),
+                             error);
+  g_error_free (error);
+  va_end (args);
+}
+
+void
+gtk_css_parser_warn (GtkCssParser         *self,
+                     GtkCssParserWarning   code,
+                     const GtkCssLocation *start,
+                     const GtkCssLocation *end,
+                     const char           *format,
+                     ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
+                              code,
+                              format, args);
+  gtk_css_parser_emit_error (self, start, end, error);
+  g_error_free (error);
+  va_end (args);
+}
+
+void
+gtk_css_parser_warn_syntax (GtkCssParser *self,
+                            const char   *format,
+                            ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
+                              GTK_CSS_PARSER_WARNING_SYNTAX,
+                              format, args);
+  gtk_css_parser_emit_error (self,
+                             gtk_css_parser_get_start_location (self),
+                             gtk_css_parser_get_end_location (self),
+                             error);
+  g_error_free (error);
+  va_end (args);
+}
+
+gboolean
+gtk_css_parser_consume_function (GtkCssParser *self,
+                                 guint         min_args,
+                                 guint         max_args,
+                                 guint (* parse_func) (GtkCssParser *, guint, gpointer),
+                                 gpointer      data)
+{
+  const GtkCssToken *token;
+  gboolean result = FALSE;
+  char *function_name;
+  guint arg;
+
+  token = gtk_css_parser_get_token (self);
+  g_return_val_if_fail (gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION), FALSE);
+
+  function_name = g_strdup (token->string.string);
+  gtk_css_parser_start_block (self);
+
+  arg = 0;
+  while (TRUE)
+    {
+      guint parse_args = parse_func (self, arg, data);
+      if (parse_args == 0)
+        break;
+      arg += parse_args;
+      token = gtk_css_parser_get_token (self);
+      if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
+        {
+          if (arg < min_args)
+            {
+              gtk_css_parser_error_syntax (self, "%s() requires at least %u arguments", function_name, 
min_args);
+              break;
+            }
+          else
+            {
+              result = TRUE;
+              break;
+            }
+        }
+      else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COMMA))
+        {
+          if (arg >= max_args)
+            {
+              gtk_css_parser_error_syntax (self, "Expected ')' at end of %s()", function_name);
+              break;
+            }
+
+          gtk_css_parser_consume_token (self);
+          continue;
+        }
+      else
+        {
+          gtk_css_parser_error_syntax (self, "Unexpected data at end of %s() argument", function_name);
+          break;
+        }
+    }
+
+  gtk_css_parser_end_block (self);
+  g_free (function_name);
+
+  return result;
+}
+
+/**
+ * gtk_css_parser_has_token:
+ * @self: a `GtkCssParser`
+ * @token_type: type of the token to check
+ *
+ * Checks if the next token is of @token_type.
+ *
+ * Returns: %TRUE if the next token is of @token_type
+ **/
+gboolean
+gtk_css_parser_has_token (GtkCssParser    *self,
+                          GtkCssTokenType  token_type)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  return gtk_css_token_is (token, token_type);
+}
+
+/**
+ * gtk_css_parser_has_ident:
+ * @self: a `GtkCssParser`
+ * @ident: name of identifier
+ *
+ * Checks if the next token is an identifier with the given @name.
+ *
+ * Returns: %TRUE if the next token is an identifier with the given @name
+ **/
+gboolean
+gtk_css_parser_has_ident (GtkCssParser *self,
+                          const char   *ident)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  return gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) &&
+         g_ascii_strcasecmp (token->string.string, ident) == 0;
+}
+
+gboolean
+gtk_css_parser_has_integer (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  return gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
+         gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER);
+}
+
+/**
+ * gtk_css_parser_has_function:
+ * @self: a `GtkCssParser`
+ * @name: name of function
+ *
+ * Checks if the next token is a function with the given @name.
+ *
+ * Returns: %TRUE if the next token is a function with the given @name
+ **/
+gboolean
+gtk_css_parser_has_function (GtkCssParser *self,
+                             const char   *name)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  return gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION) &&
+         g_ascii_strcasecmp (token->string.string, name) == 0;
+}
+
+/**
+ * gtk_css_parser_try_delim:
+ * @self: a `GtkCssParser`
+ * @codepoint: unicode character codepoint to check
+ *
+ * Checks if the current token is a delimiter matching the given
+ * @codepoint. If that is the case, the token is consumed and
+ * %TRUE is returned.
+ *
+ * Keep in mind that not every unicode codepoint can be a delim
+ * token.
+ *
+ * Returns: %TRUE if the token matched and was consumed.
+ **/
+gboolean
+gtk_css_parser_try_delim (GtkCssParser *self,
+                          gunichar      codepoint)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_DELIM) ||
+      codepoint != token->delim.delim)
+    return FALSE;
+
+  gtk_css_parser_consume_token (self);
+  return TRUE;
+}
+
+/**
+ * gtk_css_parser_try_ident:
+ * @self: a `GtkCssParser`
+ * @ident: identifier to check for
+ *
+ * Checks if the current token is an identifier matching the given
+ * @ident string. If that is the case, the token is consumed
+ * and %TRUE is returned.
+ *
+ * Returns: %TRUE if the token matched and was consumed.
+ **/
+gboolean
+gtk_css_parser_try_ident (GtkCssParser *self,
+                          const char   *ident)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) ||
+      g_ascii_strcasecmp (token->string.string, ident) != 0)
+    return FALSE;
+
+  gtk_css_parser_consume_token (self);
+  return TRUE;
+}
+
+/**
+ * gtk_css_parser_try_at_keyword:
+ * @self: a `GtkCssParser`
+ * @keyword: name of keyword to check for
+ *
+ * Checks if the current token is an at-keyword token with the
+ * given @keyword. If that is the case, the token is consumed
+ * and %TRUE is returned.
+ *
+ * Returns: %TRUE if the token matched and was consumed.
+ **/
+gboolean
+gtk_css_parser_try_at_keyword (GtkCssParser *self,
+                               const char   *keyword)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_AT_KEYWORD) ||
+      g_ascii_strcasecmp (token->string.string, keyword) != 0)
+    return FALSE;
+
+  gtk_css_parser_consume_token (self);
+  return TRUE;
+}
+
+/**
+ * gtk_css_parser_try_token:
+ * @self: a `GtkCssParser`
+ * @token_type: type of token to try
+ *
+ * Consumes the next token if it matches the given @token_type.
+ *
+ * This function can be used in loops like this:
+ * do {
+ *   ... parse one element ...
+ * } while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA);
+ *
+ * Returns: %TRUE if a token was consumed
+ **/
+gboolean
+gtk_css_parser_try_token (GtkCssParser    *self,
+                          GtkCssTokenType  token_type)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, token_type))
+    return FALSE;
+
+  gtk_css_parser_consume_token (self);
+  return TRUE;
+}
+
+/**
+ * gtk_css_parser_consume_ident:
+ * @self: a `GtkCssParser`
+ *
+ * If the current token is an identifier, consumes it and returns
+ * its name.
+ *
+ * If the current token is not an identifier, an error is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): the name of the consumed identifier
+ */
+char *
+gtk_css_parser_consume_ident (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+  char *ident;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+    {
+      gtk_css_parser_error_syntax (self, "Expected an identifier");
+      return NULL;
+    }
+
+  ident = g_strdup (token->string.string);
+  gtk_css_parser_consume_token (self);
+
+  return ident;
+}
+
+/**
+ * gtk_css_parser_consume_string:
+ * @self: a `GtkCssParser`
+ *
+ * If the current token is a string, consumes it and return the string.
+ *
+ * If the current token is not a string, an error is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): the name of the consumed string
+ **/
+char *
+gtk_css_parser_consume_string (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+  char *ident;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
+    {
+      gtk_css_parser_error_syntax (self, "Expected a string");
+      return NULL;
+    }
+
+  ident = g_strdup (token->string.string);
+  gtk_css_parser_consume_token (self);
+
+  return ident;
+}
+
+static guint
+gtk_css_parser_parse_url_arg (GtkCssParser *parser,
+                              guint         arg,
+                              gpointer      data)
+{
+  char **out_url = data;
+
+  *out_url = gtk_css_parser_consume_string (parser);
+  if (*out_url == NULL)
+    return 0;
+  
+  return 1;
+}
+
+/**
+ * gtk_css_parser_consume_url:
+ * @self: a `GtkCssParser`
+ *
+ * If the parser matches the <url> token from the [CSS
+ * specification](https://drafts.csswg.org/css-values-4/#url-value),
+ * consumes it, resolves the URL and returns the resulting `GFile`.
+ * On failure, an error is emitted and %NULL is returned.
+ *
+ * Returns: (nullable) (transfer full): the resulting URL
+ **/
+char *
+gtk_css_parser_consume_url (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+  char *url;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_URL))
+    {
+      url = g_strdup (token->string.string);
+      gtk_css_parser_consume_token (self);
+    }
+  else if (gtk_css_token_is_function (token, "url"))
+    {
+      if (!gtk_css_parser_consume_function (self, 1, 1, gtk_css_parser_parse_url_arg, &url))
+        return NULL;
+    }
+  else
+    {
+      gtk_css_parser_error_syntax (self, "Expected a URL");
+      return NULL;
+    }
+  
+  return url;
+}
+
+gboolean
+gtk_css_parser_has_number (GtkCssParser *self)
+{
+  return gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNED_NUMBER)
+      || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNLESS_NUMBER)
+      || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNED_INTEGER)
+      || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNLESS_INTEGER);
+}
+
+gboolean
+gtk_css_parser_consume_number (GtkCssParser *self,
+                               double       *number)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
+      gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_NUMBER) ||
+      gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
+      gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
+    {
+      *number = token->number.number;
+      gtk_css_parser_consume_token (self);
+      return TRUE;
+    }
+
+  gtk_css_parser_error_syntax (self, "Expected a number");
+  /* FIXME: Implement calc() */
+  return FALSE;
+}
+
+gboolean
+gtk_css_parser_consume_integer (GtkCssParser *self,
+                                int          *number)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
+      gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
+    {
+      *number = token->number.number;
+      gtk_css_parser_consume_token (self);
+      return TRUE;
+    }
+
+  gtk_css_parser_error_syntax (self, "Expected an integer");
+  /* FIXME: Implement calc() */
+  return FALSE;
+}
+
+gboolean
+gtk_css_parser_consume_percentage (GtkCssParser *self,
+                                   double       *number)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_PERCENTAGE))
+    {
+      *number = token->number.number;
+      gtk_css_parser_consume_token (self);
+      return TRUE;
+    }
+
+  gtk_css_parser_error_syntax (self, "Expected a percentage");
+  /* FIXME: Implement calc() */
+  return FALSE;
+}
+
+gsize
+gtk_css_parser_consume_any (GtkCssParser            *parser,
+                            const GtkCssParseOption *options,
+                            gsize                    n_options,
+                            gpointer                 user_data)
+{
+  gsize result;
+  gsize i;
+
+  g_return_val_if_fail (parser != NULL, 0);
+  g_return_val_if_fail (options != NULL, 0);
+  g_return_val_if_fail (n_options < sizeof (gsize) * 8 - 1, 0);
+
+  result = 0;
+  while (result != (1u << n_options) - 1u)
+    {
+      for (i = 0; i < n_options; i++)
+        {
+          if (result & (1 << i))
+            continue;
+          if (options[i].can_parse && !options[i].can_parse (parser, options[i].data, user_data))
+            continue;
+          if (!options[i].parse (parser, options[i].data, user_data))
+            return 0;
+          result |= 1 << i;
+          break;
+        }
+      if (i == n_options)
+        break;
+    }
+
+  if (result == 0)
+    {
+      gtk_css_parser_error_syntax (parser, "No valid value given");
+      return result;
+    }
+
+  return result;
+}
diff --git a/pango/css/gtkcssparserprivate.h b/pango/css/gtkcssparserprivate.h
new file mode 100644
index 00000000..5659fe0c
--- /dev/null
+++ b/pango/css/gtkcssparserprivate.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#ifndef __GTK_CSS_PARSER_H__
+#define __GTK_CSS_PARSER_H__
+
+#include "gtkcssenums.h"
+#include "gtkcsstokenizerprivate.h"
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkCssParser GtkCssParser;
+
+typedef struct _GtkCssParseOption GtkCssParseOption;
+
+struct _GtkCssParseOption
+{
+  gboolean (* can_parse)  (GtkCssParser *parser,
+                           gpointer      option_data,
+                           gpointer      user_data);
+  gboolean (* parse)      (GtkCssParser *parser,
+                           gpointer      option_data,
+                           gpointer      user_data);
+  gpointer data;
+};
+
+typedef void            (* GtkCssParserErrorFunc)               (GtkCssParser                   *parser,
+                                                                 const GtkCssLocation           *start,
+                                                                 const GtkCssLocation           *end,
+                                                                 const GError                   *error,
+                                                                 gpointer                        user_data);
+
+GtkCssParser *          gtk_css_parser_new_for_file             (GFile                          *file,
+                                                                 GtkCssParserErrorFunc           error_func,
+                                                                 gpointer                        user_data,
+                                                                 GDestroyNotify                  
user_destroy,
+                                                                 GError                        **error);
+GtkCssParser *          gtk_css_parser_new_for_bytes            (GBytes                         *bytes,
+                                                                 GFile                          *file,
+                                                                 GtkCssParserErrorFunc           error_func,
+                                                                 gpointer                        user_data,
+                                                                 GDestroyNotify                  
user_destroy);
+GtkCssParser *          gtk_css_parser_ref                      (GtkCssParser                   *self);
+void                    gtk_css_parser_unref                    (GtkCssParser                   *self);
+
+GFile *                 gtk_css_parser_get_file                 (GtkCssParser                   *self) 
G_GNUC_PURE;
+GFile *                 gtk_css_parser_resolve_url              (GtkCssParser                   *self,
+                                                                 const char                     *url);
+
+const GtkCssLocation *  gtk_css_parser_get_start_location       (GtkCssParser                   *self) 
G_GNUC_PURE;
+const GtkCssLocation *  gtk_css_parser_get_end_location         (GtkCssParser                   *self) 
G_GNUC_PURE;
+const GtkCssLocation *  gtk_css_parser_get_block_location       (GtkCssParser                   *self) 
G_GNUC_PURE;
+
+const GtkCssToken *     gtk_css_parser_peek_token               (GtkCssParser                   *self);
+const GtkCssToken *     gtk_css_parser_get_token                (GtkCssParser                   *self);
+void                    gtk_css_parser_consume_token            (GtkCssParser                   *self);
+
+void                    gtk_css_parser_start_block              (GtkCssParser                   *self); 
+void                    gtk_css_parser_start_semicolon_block    (GtkCssParser                   *self,
+                                                                 GtkCssTokenType                 
alternative_token);
+void                    gtk_css_parser_end_block_prelude        (GtkCssParser                   *self);
+void                    gtk_css_parser_end_block                (GtkCssParser                   *self); 
+void                    gtk_css_parser_skip                     (GtkCssParser                   *self);
+void                    gtk_css_parser_skip_until               (GtkCssParser                   *self,
+                                                                 GtkCssTokenType                 token_type);
+
+void                    gtk_css_parser_emit_error               (GtkCssParser                   *self,
+                                                                 const GtkCssLocation           *start,
+                                                                 const GtkCssLocation           *end,
+                                                                 const GError                   *error);
+void                    gtk_css_parser_error                    (GtkCssParser                   *self,
+                                                                 GtkCssParserError               code,
+                                                                 const GtkCssLocation           *start,
+                                                                 const GtkCssLocation           *end,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(5, 6);
+void                    gtk_css_parser_error_syntax             (GtkCssParser                   *self,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+void                    gtk_css_parser_error_value              (GtkCssParser                   *self,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+void                    gtk_css_parser_error_import             (GtkCssParser                   *self,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+void                    gtk_css_parser_warn                     (GtkCssParser                   *self,
+                                                                 GtkCssParserWarning             code,
+                                                                 const GtkCssLocation           *start,
+                                                                 const GtkCssLocation           *end,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(5, 6);
+void                    gtk_css_parser_warn_syntax              (GtkCssParser                   *self,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+
+
+gboolean                gtk_css_parser_has_token                (GtkCssParser                   *self,
+                                                                 GtkCssTokenType                 token_type);
+gboolean                gtk_css_parser_has_ident                (GtkCssParser                   *self,
+                                                                 const char                     *ident);
+gboolean                gtk_css_parser_has_number               (GtkCssParser                   *self);
+gboolean                gtk_css_parser_has_integer              (GtkCssParser                   *self);
+gboolean                gtk_css_parser_has_function             (GtkCssParser                   *self,
+                                                                 const char                     *name);
+
+gboolean                gtk_css_parser_try_delim                (GtkCssParser                   *self,
+                                                                 gunichar                        codepoint);
+gboolean                gtk_css_parser_try_ident                (GtkCssParser                   *self,
+                                                                 const char                     *ident);
+gboolean                gtk_css_parser_try_at_keyword           (GtkCssParser                   *self,
+                                                                 const char                     *keyword);
+gboolean                gtk_css_parser_try_token                (GtkCssParser                   *self,
+                                                                 GtkCssTokenType                 token_type);
+
+char *                  gtk_css_parser_consume_ident            (GtkCssParser                   *self) 
G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+char *                  gtk_css_parser_consume_string           (GtkCssParser                   *self) 
G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+char *                  gtk_css_parser_consume_url              (GtkCssParser                   *self) 
G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+gboolean                gtk_css_parser_consume_number           (GtkCssParser                   *self,
+                                                                 double                         *number);
+gboolean                gtk_css_parser_consume_integer          (GtkCssParser                   *self,
+                                                                 int                            *number);
+gboolean                gtk_css_parser_consume_percentage       (GtkCssParser                   *self,
+                                                                 double                         *number);
+gboolean                gtk_css_parser_consume_function         (GtkCssParser                   *self,
+                                                                 guint                           min_args,
+                                                                 guint                           max_args,
+                                                                 guint (* parse_func) (GtkCssParser *, 
guint, gpointer),
+                                                                 gpointer                        data);
+gsize                   gtk_css_parser_consume_any              (GtkCssParser                   *parser,
+                                                                 const GtkCssParseOption        *options,
+                                                                 gsize                           n_options,
+                                                                 gpointer                        user_data);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_PARSER_H__ */
diff --git a/pango/css/gtkcsssection.c b/pango/css/gtkcsssection.c
new file mode 100644
index 00000000..5851cc20
--- /dev/null
+++ b/pango/css/gtkcsssection.c
@@ -0,0 +1,256 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkcsssection.h"
+
+#include "gtkcssparserprivate.h"
+
+struct _GtkCssSection
+{
+  int                 ref_count;
+  GtkCssSection      *parent;
+  GFile              *file;
+  GtkCssLocation      start_location;
+  GtkCssLocation      end_location;   /* end location if parser is %NULL */
+};
+
+G_DEFINE_BOXED_TYPE (GtkCssSection, gtk_css_section, gtk_css_section_ref, gtk_css_section_unref)
+
+/**
+ * gtk_css_section_new: (constructor)
+ * @file: (nullable) (transfer none): The file this section refers to
+ * @start: The start location
+ * @end: The end location
+ *
+ * Creates a new `GtkCssSection` referring to the section
+ * in the given `file` from the `start` location to the
+ * `end` location.
+ *
+ * Returns: (transfer full): a new `GtkCssSection`
+ **/
+GtkCssSection *
+gtk_css_section_new (GFile                *file,
+                     const GtkCssLocation *start,
+                     const GtkCssLocation *end)
+{
+  GtkCssSection *result;
+
+  g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
+  g_return_val_if_fail (start != NULL, NULL);
+  g_return_val_if_fail (end != NULL, NULL);
+
+  result = g_slice_new0 (GtkCssSection);
+
+  result->ref_count = 1;
+  if (file)
+    result->file = g_object_ref (file);
+  result->start_location = *start;
+  result->end_location = *end;
+
+  return result;
+}
+
+/**
+ * gtk_css_section_ref:
+ * @section: a `GtkCssSection`
+ *
+ * Increments the reference count on `section`.
+ *
+ * Returns: (transfer full): the CSS section itself.
+ **/
+GtkCssSection *
+gtk_css_section_ref (GtkCssSection *section)
+{
+  g_return_val_if_fail (section != NULL, NULL);
+
+  section->ref_count += 1;
+
+  return section;
+}
+
+/**
+ * gtk_css_section_unref:
+ * @section: (transfer full): a `GtkCssSection`
+ *
+ * Decrements the reference count on `section`, freeing the
+ * structure if the reference count reaches 0.
+ **/
+void
+gtk_css_section_unref (GtkCssSection *section)
+{
+  g_return_if_fail (section != NULL);
+
+  section->ref_count -= 1;
+  if (section->ref_count > 0)
+    return;
+
+  if (section->parent)
+    gtk_css_section_unref (section->parent);
+  if (section->file)
+    g_object_unref (section->file);
+
+  g_slice_free (GtkCssSection, section);
+}
+
+/**
+ * gtk_css_section_get_parent:
+ * @section: the section
+ *
+ * Gets the parent section for the given `section`.
+ *
+ * The parent section is the section that contains this `section`. A special
+ * case are sections of  type `GTK_CSS_SECTION_DOCUMEN`T. Their parent will
+ * either be `NULL` if they are the original CSS document that was loaded by
+ * [method@Gtk.CssProvider.load_from_file] or a section of type
+ * `GTK_CSS_SECTION_IMPORT` if it was loaded with an `@import` rule from
+ * a different file.
+ *
+ * Returns: (nullable) (transfer none): the parent section
+ **/
+GtkCssSection *
+gtk_css_section_get_parent (const GtkCssSection *section)
+{
+  g_return_val_if_fail (section != NULL, NULL);
+
+  return section->parent;
+}
+
+/**
+ * gtk_css_section_get_file:
+ * @section: the section
+ *
+ * Gets the file that @section was parsed from.
+ *
+ * If no such file exists, for example because the CSS was loaded via
+ * [method@Gtk.CssProvider.load_from_data], then `NULL` is returned.
+ *
+ * Returns: (transfer none): the `GFile` from which the `section`
+ *   was parsed
+ **/
+GFile *
+gtk_css_section_get_file (const GtkCssSection *section)
+{
+  g_return_val_if_fail (section != NULL, NULL);
+
+  return section->file;
+}
+
+/**
+ * gtk_css_section_get_start_location:
+ * @section: the section
+ *
+ * Returns the location in the CSS document where this section starts.
+ *
+ * Returns: (transfer none) (not nullable): The start location of
+ *   this section
+ */
+const GtkCssLocation *
+gtk_css_section_get_start_location (const GtkCssSection *section)
+{
+  g_return_val_if_fail (section != NULL, NULL);
+
+  return &section->start_location;
+}
+
+/**
+ * gtk_css_section_get_end_location:
+ * @section: the section
+ *
+ * Returns the location in the CSS document where this section ends.
+ *
+ * Returns: (transfer none) (not nullable): The end location of
+ *   this section
+ */
+const GtkCssLocation *
+gtk_css_section_get_end_location (const GtkCssSection *section)
+{
+  g_return_val_if_fail (section != NULL, NULL);
+
+  return &section->end_location;
+}
+
+/**
+ * gtk_css_section_print:
+ * @section: a section
+ * @string: a `GString` to print to
+ *
+ * Prints the `section` into `string` in a human-readable form.
+ *
+ * This is a form like `gtk.css:32:1-23` to denote line 32, characters
+ * 1 to 23 in the file `gtk.css`.
+ **/
+void
+gtk_css_section_print (const GtkCssSection  *section,
+                       GString              *string)
+{
+  if (section->file)
+    {
+      GFileInfo *info;
+
+      info = g_file_query_info (section->file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, 0, NULL, NULL);
+
+      if (info)
+        {
+          g_string_append (string, g_file_info_get_display_name (info));
+          g_object_unref (info);
+        }
+      else
+        {
+          g_string_append (string, "<broken file>");
+        }
+    }
+  else
+    {
+      g_string_append (string, "<data>");
+    }
+
+  g_string_append_printf (string, ":%zu:%zu", 
+                          section->start_location.lines + 1,
+                          section->start_location.line_chars + 1);
+  if (section->start_location.lines != section->end_location.lines ||
+      section->start_location.line_chars != section->end_location.line_chars)
+    {
+      g_string_append (string, "-");
+      if (section->start_location.lines != section->end_location.lines)
+        g_string_append_printf (string, "%zu:", section->end_location.lines + 1);
+      g_string_append_printf (string, "%zu", section->end_location.line_chars + 1);
+    }
+}
+
+/**
+ * gtk_css_section_to_string:
+ * @section: a `GtkCssSection`
+ *
+ * Prints the section into a human-readable text form using
+ * [method@Gtk.CssSection.print].
+ *
+ * Returns: (transfer full): A new string.
+ **/
+char *
+gtk_css_section_to_string (const GtkCssSection *section)
+{
+  GString *string;
+
+  g_return_val_if_fail (section != NULL, NULL);
+
+  string = g_string_new (NULL);
+  gtk_css_section_print (section, string);
+
+  return g_string_free (string, FALSE);
+}
diff --git a/pango/css/gtkcsssection.h b/pango/css/gtkcsssection.h
new file mode 100644
index 00000000..8892a2d8
--- /dev/null
+++ b/pango/css/gtkcsssection.h
@@ -0,0 +1,59 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_CSS_SECTION_H__
+#define __GTK_CSS_SECTION_H__
+
+#include <gio/gio.h>
+#include <pango/css/gtkcsslocation.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_CSS_SECTION         (gtk_css_section_get_type ())
+
+/**
+ * GtkCssSection:
+ *
+ * Defines a part of a CSS document.
+ *
+ * Because sections are nested into one another, you can use
+ * gtk_css_section_get_parent() to get the containing region.
+ */
+typedef struct _GtkCssSection GtkCssSection;
+
+GType              gtk_css_section_get_type            (void) G_GNUC_CONST;
+
+GtkCssSection *    gtk_css_section_new                 (GFile                *file,
+                                                        const GtkCssLocation *start,
+                                                        const GtkCssLocation *end);
+GtkCssSection *    gtk_css_section_ref                 (GtkCssSection        *section);
+void               gtk_css_section_unref               (GtkCssSection        *section);
+
+void               gtk_css_section_print               (const GtkCssSection  *section,
+                                                        GString              *string);
+char *             gtk_css_section_to_string           (const GtkCssSection  *section);
+
+GtkCssSection *    gtk_css_section_get_parent          (const GtkCssSection  *section);
+GFile *            gtk_css_section_get_file            (const GtkCssSection  *section);
+const GtkCssLocation *
+                   gtk_css_section_get_start_location  (const GtkCssSection  *section);
+const GtkCssLocation *
+                   gtk_css_section_get_end_location    (const GtkCssSection  *section);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_SECTION_H__ */
diff --git a/pango/css/gtkcssserializer.c b/pango/css/gtkcssserializer.c
new file mode 100644
index 00000000..d939f38c
--- /dev/null
+++ b/pango/css/gtkcssserializer.c
@@ -0,0 +1,72 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkcssserializerprivate.h"
+
+/* Escape a string so that it can be parsed
+ * as a css string again.
+ */
+void
+gtk_css_print_string (GString    *str,
+                      const char *string,
+                      gboolean    multiline)
+{
+  gsize len;
+
+  g_return_if_fail (str != NULL);
+  g_return_if_fail (string != NULL);
+
+  g_string_append_c (str, '"');
+
+  do {
+    len = strcspn (string, "\\\"\n\r\f");
+    g_string_append_len (str, string, len);
+    string += len;
+    switch (*string)
+      {
+      case '\0':
+        goto out;
+      case '\n':
+        if (multiline)
+          g_string_append (str, "\\A\\\n");
+        else
+          g_string_append (str, "\\A ");
+        break;
+      case '\r':
+        g_string_append (str, "\\D ");
+        break;
+      case '\f':
+        g_string_append (str, "\\C ");
+        break;
+      case '\"':
+        g_string_append (str, "\\\"");
+        break;
+      case '\\':
+        g_string_append (str, "\\\\");
+        break;
+      default:
+        g_assert_not_reached ();
+        break;
+      }
+    string++;
+  } while (*string);
+
+out:
+  g_string_append_c (str, '"');
+}
diff --git a/pango/css/gtkcssserializerprivate.h b/pango/css/gtkcssserializerprivate.h
new file mode 100644
index 00000000..86f9934f
--- /dev/null
+++ b/pango/css/gtkcssserializerprivate.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2020 Red Hat, Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen <mclasen redhat com>
+ */
+
+
+#ifndef __GTK_CSS_SERIALIZER_H__
+#define __GTK_CSS_SERIALIZER_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void gtk_css_print_string (GString    *str,
+                           const char *string,
+                           gboolean    multiline);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_SERIALIZER_H__ */
diff --git a/pango/css/gtkcsstokenizer.c b/pango/css/gtkcsstokenizer.c
new file mode 100644
index 00000000..a6d14abc
--- /dev/null
+++ b/pango/css/gtkcsstokenizer.c
@@ -0,0 +1,1487 @@
+/* GSK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkcsstokenizerprivate.h"
+
+#include "gtkcssenums.h"
+#include "gtkcsserror.h"
+#include "gtkcsslocationprivate.h"
+
+#include <math.h>
+#include <string.h>
+
+struct _GtkCssTokenizer
+{
+  int                    ref_count;
+  GBytes                *bytes;
+  GString               *name_buffer;
+
+  const char            *data;
+  const char            *end;
+
+  GtkCssLocation         position;
+};
+
+void
+gtk_css_token_clear (GtkCssToken *token)
+{
+  switch (token->type)
+    {
+    case GTK_CSS_TOKEN_STRING:
+    case GTK_CSS_TOKEN_IDENT:
+    case GTK_CSS_TOKEN_FUNCTION:
+    case GTK_CSS_TOKEN_AT_KEYWORD:
+    case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
+    case GTK_CSS_TOKEN_HASH_ID:
+    case GTK_CSS_TOKEN_URL:
+      g_free (token->string.string);
+      break;
+
+    case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNED_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
+      g_free (token->dimension.dimension);
+      break;
+
+    default:
+      g_assert_not_reached ();
+    case GTK_CSS_TOKEN_EOF:
+    case GTK_CSS_TOKEN_WHITESPACE:
+    case GTK_CSS_TOKEN_OPEN_PARENS:
+    case GTK_CSS_TOKEN_CLOSE_PARENS:
+    case GTK_CSS_TOKEN_OPEN_SQUARE:
+    case GTK_CSS_TOKEN_CLOSE_SQUARE:
+    case GTK_CSS_TOKEN_OPEN_CURLY:
+    case GTK_CSS_TOKEN_CLOSE_CURLY:
+    case GTK_CSS_TOKEN_COMMA:
+    case GTK_CSS_TOKEN_COLON:
+    case GTK_CSS_TOKEN_SEMICOLON:
+    case GTK_CSS_TOKEN_CDC:
+    case GTK_CSS_TOKEN_CDO:
+    case GTK_CSS_TOKEN_DELIM:
+    case GTK_CSS_TOKEN_SIGNED_INTEGER:
+    case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
+    case GTK_CSS_TOKEN_SIGNED_NUMBER:
+    case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
+    case GTK_CSS_TOKEN_PERCENTAGE:
+    case GTK_CSS_TOKEN_INCLUDE_MATCH:
+    case GTK_CSS_TOKEN_DASH_MATCH:
+    case GTK_CSS_TOKEN_PREFIX_MATCH:
+    case GTK_CSS_TOKEN_SUFFIX_MATCH:
+    case GTK_CSS_TOKEN_SUBSTRING_MATCH:
+    case GTK_CSS_TOKEN_COLUMN:
+    case GTK_CSS_TOKEN_BAD_STRING:
+    case GTK_CSS_TOKEN_BAD_URL:
+    case GTK_CSS_TOKEN_COMMENT:
+      break;
+    }
+
+  token->type = GTK_CSS_TOKEN_EOF;
+}
+
+static void
+gtk_css_token_init (GtkCssToken     *token,
+                    GtkCssTokenType  type)
+{
+  token->type = type;
+
+  switch ((guint)type)
+    {
+    case GTK_CSS_TOKEN_EOF:
+    case GTK_CSS_TOKEN_WHITESPACE:
+    case GTK_CSS_TOKEN_OPEN_PARENS:
+    case GTK_CSS_TOKEN_CLOSE_PARENS:
+    case GTK_CSS_TOKEN_OPEN_SQUARE:
+    case GTK_CSS_TOKEN_CLOSE_SQUARE:
+    case GTK_CSS_TOKEN_OPEN_CURLY:
+    case GTK_CSS_TOKEN_CLOSE_CURLY:
+    case GTK_CSS_TOKEN_COMMA:
+    case GTK_CSS_TOKEN_COLON:
+    case GTK_CSS_TOKEN_SEMICOLON:
+    case GTK_CSS_TOKEN_CDC:
+    case GTK_CSS_TOKEN_CDO:
+    case GTK_CSS_TOKEN_INCLUDE_MATCH:
+    case GTK_CSS_TOKEN_DASH_MATCH:
+    case GTK_CSS_TOKEN_PREFIX_MATCH:
+    case GTK_CSS_TOKEN_SUFFIX_MATCH:
+    case GTK_CSS_TOKEN_SUBSTRING_MATCH:
+    case GTK_CSS_TOKEN_COLUMN:
+    case GTK_CSS_TOKEN_BAD_STRING:
+    case GTK_CSS_TOKEN_BAD_URL:
+    case GTK_CSS_TOKEN_COMMENT:
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+append_ident (GString    *string,
+              const char *ident)
+{
+  /* XXX */
+  g_string_append (string, ident);
+}
+
+static void
+append_string (GString    *string,
+               const char *s)
+{
+  g_string_append_c (string, '"');
+  /* XXX */
+  g_string_append (string, s);
+  g_string_append_c (string, '"');
+}
+
+/*
+ * gtk_css_token_is_finite:
+ * @token: a `GtkCssToken`
+ *
+ * A token is considered finite when it would stay the same no matter
+ * what bytes follow it in the data stream.
+ *
+ * An obvious example for this is the ';' token.
+ *
+ * Returns: %TRUE if the token is considered finite.
+ **/
+gboolean
+gtk_css_token_is_finite (const GtkCssToken *token)
+{
+  switch (token->type)
+    {
+    case GTK_CSS_TOKEN_EOF:
+    case GTK_CSS_TOKEN_STRING:
+    case GTK_CSS_TOKEN_FUNCTION:
+    case GTK_CSS_TOKEN_URL:
+    case GTK_CSS_TOKEN_PERCENTAGE:
+    case GTK_CSS_TOKEN_OPEN_PARENS:
+    case GTK_CSS_TOKEN_CLOSE_PARENS:
+    case GTK_CSS_TOKEN_OPEN_SQUARE:
+    case GTK_CSS_TOKEN_CLOSE_SQUARE:
+    case GTK_CSS_TOKEN_OPEN_CURLY:
+    case GTK_CSS_TOKEN_CLOSE_CURLY:
+    case GTK_CSS_TOKEN_COMMA:
+    case GTK_CSS_TOKEN_COLON:
+    case GTK_CSS_TOKEN_SEMICOLON:
+    case GTK_CSS_TOKEN_CDC:
+    case GTK_CSS_TOKEN_CDO:
+    case GTK_CSS_TOKEN_INCLUDE_MATCH:
+    case GTK_CSS_TOKEN_DASH_MATCH:
+    case GTK_CSS_TOKEN_PREFIX_MATCH:
+    case GTK_CSS_TOKEN_SUFFIX_MATCH:
+    case GTK_CSS_TOKEN_SUBSTRING_MATCH:
+    case GTK_CSS_TOKEN_COLUMN:
+    case GTK_CSS_TOKEN_COMMENT:
+      return TRUE;
+
+    default:
+      g_assert_not_reached ();
+    case GTK_CSS_TOKEN_WHITESPACE:
+    case GTK_CSS_TOKEN_IDENT:
+    case GTK_CSS_TOKEN_AT_KEYWORD:
+    case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
+    case GTK_CSS_TOKEN_HASH_ID:
+    case GTK_CSS_TOKEN_DELIM:
+    case GTK_CSS_TOKEN_SIGNED_INTEGER:
+    case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
+    case GTK_CSS_TOKEN_SIGNED_NUMBER:
+    case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
+    case GTK_CSS_TOKEN_BAD_STRING:
+    case GTK_CSS_TOKEN_BAD_URL:
+    case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNED_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
+      return FALSE;
+    }
+}
+
+/*
+ * gtk_css_token_is_preserved:
+ * @token: a `GtkCssToken`
+ * @out_closing: (nullable): Type of the token that closes a block
+ *   started with this token
+ *
+ * A token is considered preserved when it does not start a block.
+ *
+ * Tokens that start a block require different error recovery when parsing,
+ * so CSS parsers want to look at this function
+ *
+ * Returns: %TRUE if the token is considered preserved.
+ */
+gboolean
+gtk_css_token_is_preserved (const GtkCssToken *token,
+                            GtkCssTokenType   *out_closing)
+{
+  switch (token->type)
+    {
+    case GTK_CSS_TOKEN_FUNCTION:
+    case GTK_CSS_TOKEN_OPEN_PARENS:
+      if (out_closing)
+        *out_closing = GTK_CSS_TOKEN_CLOSE_PARENS;
+      return FALSE;
+
+    case GTK_CSS_TOKEN_OPEN_SQUARE:
+      if (out_closing)
+        *out_closing = GTK_CSS_TOKEN_CLOSE_SQUARE;
+      return FALSE;
+
+    case GTK_CSS_TOKEN_OPEN_CURLY:
+      if (out_closing)
+        *out_closing = GTK_CSS_TOKEN_CLOSE_CURLY;
+      return FALSE;
+
+    default:
+      g_assert_not_reached ();
+    case GTK_CSS_TOKEN_EOF:
+    case GTK_CSS_TOKEN_WHITESPACE:
+    case GTK_CSS_TOKEN_STRING:
+    case GTK_CSS_TOKEN_URL:
+    case GTK_CSS_TOKEN_PERCENTAGE:
+    case GTK_CSS_TOKEN_CLOSE_PARENS:
+    case GTK_CSS_TOKEN_CLOSE_SQUARE:
+    case GTK_CSS_TOKEN_CLOSE_CURLY:
+    case GTK_CSS_TOKEN_COMMA:
+    case GTK_CSS_TOKEN_COLON:
+    case GTK_CSS_TOKEN_SEMICOLON:
+    case GTK_CSS_TOKEN_CDC:
+    case GTK_CSS_TOKEN_CDO:
+    case GTK_CSS_TOKEN_INCLUDE_MATCH:
+    case GTK_CSS_TOKEN_DASH_MATCH:
+    case GTK_CSS_TOKEN_PREFIX_MATCH:
+    case GTK_CSS_TOKEN_SUFFIX_MATCH:
+    case GTK_CSS_TOKEN_SUBSTRING_MATCH:
+    case GTK_CSS_TOKEN_COLUMN:
+    case GTK_CSS_TOKEN_COMMENT:
+    case GTK_CSS_TOKEN_IDENT:
+    case GTK_CSS_TOKEN_AT_KEYWORD:
+    case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
+    case GTK_CSS_TOKEN_HASH_ID:
+    case GTK_CSS_TOKEN_DELIM:
+    case GTK_CSS_TOKEN_SIGNED_INTEGER:
+    case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
+    case GTK_CSS_TOKEN_SIGNED_NUMBER:
+    case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
+    case GTK_CSS_TOKEN_BAD_STRING:
+    case GTK_CSS_TOKEN_BAD_URL:
+    case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNED_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
+      if (out_closing)
+        *out_closing = GTK_CSS_TOKEN_EOF;
+      return TRUE;
+    }
+}
+
+gboolean
+gtk_css_token_is_ident (const GtkCssToken *token,
+                        const char        *ident)
+{
+  return gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT)
+      && (g_ascii_strcasecmp (token->string.string, ident) == 0);
+}
+
+gboolean
+gtk_css_token_is_function (const GtkCssToken *token,
+                           const char        *ident)
+{
+  return gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION)
+      && (g_ascii_strcasecmp (token->string.string, ident) == 0);
+}
+
+gboolean
+gtk_css_token_is_delim (const GtkCssToken *token,
+                        gunichar           delim)
+{
+  return gtk_css_token_is (token, GTK_CSS_TOKEN_DELIM)
+      && token->delim.delim == delim;
+}
+
+void
+gtk_css_token_print (const GtkCssToken *token,
+                     GString           *string)
+{
+  char buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+  switch (token->type)
+    {
+    case GTK_CSS_TOKEN_STRING:
+      append_string (string, token->string.string);
+      break;
+
+    case GTK_CSS_TOKEN_IDENT:
+      append_ident (string, token->string.string);
+      break;
+
+    case GTK_CSS_TOKEN_URL:
+      g_string_append (string, "url(");
+      append_ident (string, token->string.string);
+      g_string_append (string, ")");
+      break;
+
+    case GTK_CSS_TOKEN_FUNCTION:
+      append_ident (string, token->string.string);
+      g_string_append_c (string, '(');
+      break;
+
+    case GTK_CSS_TOKEN_AT_KEYWORD:
+      g_string_append_c (string, '@');
+      append_ident (string, token->string.string);
+      break;
+
+    case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
+    case GTK_CSS_TOKEN_HASH_ID:
+      g_string_append_c (string, '#');
+      append_ident (string, token->string.string);
+      break;
+
+    case GTK_CSS_TOKEN_DELIM:
+      g_string_append_unichar (string, token->delim.delim);
+      break;
+
+    case GTK_CSS_TOKEN_SIGNED_INTEGER:
+    case GTK_CSS_TOKEN_SIGNED_NUMBER:
+      if (token->number.number >= 0)
+        g_string_append_c (string, '+');
+      G_GNUC_FALLTHROUGH;
+    case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
+    case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
+      g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, token->number.number);
+      g_string_append (string, buf);
+      break;
+
+    case GTK_CSS_TOKEN_PERCENTAGE:
+      g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, token->number.number);
+      g_string_append (string, buf);
+      g_string_append_c (string, '%');
+      break;
+
+    case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNED_DIMENSION:
+      if (token->dimension.value >= 0)
+        g_string_append_c (string, '+');
+      G_GNUC_FALLTHROUGH;
+    case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
+      g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, token->dimension.value);
+      g_string_append (string, buf);
+      append_ident (string, token->dimension.dimension);
+      break;
+
+    case GTK_CSS_TOKEN_EOF:
+      break;
+
+    case GTK_CSS_TOKEN_WHITESPACE:
+      g_string_append (string, " ");
+      break;
+
+    case GTK_CSS_TOKEN_OPEN_PARENS:
+      g_string_append (string, "(");
+      break;
+
+    case GTK_CSS_TOKEN_CLOSE_PARENS:
+      g_string_append (string, ")");
+      break;
+
+    case GTK_CSS_TOKEN_OPEN_SQUARE:
+      g_string_append (string, "[");
+      break;
+
+    case GTK_CSS_TOKEN_CLOSE_SQUARE:
+      g_string_append (string, "]");
+      break;
+
+    case GTK_CSS_TOKEN_OPEN_CURLY:
+      g_string_append (string, "{");
+      break;
+
+    case GTK_CSS_TOKEN_CLOSE_CURLY:
+      g_string_append (string, "}");
+      break;
+
+    case GTK_CSS_TOKEN_COMMA:
+      g_string_append (string, ",");
+      break;
+
+    case GTK_CSS_TOKEN_COLON:
+      g_string_append (string, ":");
+      break;
+
+    case GTK_CSS_TOKEN_SEMICOLON:
+      g_string_append (string, ";");
+      break;
+
+    case GTK_CSS_TOKEN_CDO:
+      g_string_append (string, "<!--");
+      break;
+
+    case GTK_CSS_TOKEN_CDC:
+      g_string_append (string, "-->");
+      break;
+
+    case GTK_CSS_TOKEN_INCLUDE_MATCH:
+      g_string_append (string, "~=");
+      break;
+
+    case GTK_CSS_TOKEN_DASH_MATCH:
+      g_string_append (string, "|=");
+      break;
+
+    case GTK_CSS_TOKEN_PREFIX_MATCH:
+      g_string_append (string, "^=");
+      break;
+
+    case GTK_CSS_TOKEN_SUFFIX_MATCH:
+      g_string_append (string, "$=");
+      break;
+
+    case GTK_CSS_TOKEN_SUBSTRING_MATCH:
+      g_string_append (string, "*=");
+      break;
+
+    case GTK_CSS_TOKEN_COLUMN:
+      g_string_append (string, "||");
+      break;
+
+    case GTK_CSS_TOKEN_BAD_STRING:
+      g_string_append (string, "\"\n");
+      break;
+
+    case GTK_CSS_TOKEN_BAD_URL:
+      g_string_append (string, "url(bad url)");
+      break;
+
+    case GTK_CSS_TOKEN_COMMENT:
+      g_string_append (string, "/* comment */");
+      break;
+
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+char *
+gtk_css_token_to_string (const GtkCssToken *token)
+{
+  GString *string;
+
+  string = g_string_new (NULL);
+  gtk_css_token_print (token, string);
+  return g_string_free (string, FALSE);
+}
+
+static void
+gtk_css_token_init_string (GtkCssToken     *token,
+                           GtkCssTokenType  type,
+                           char            *string)
+{
+  token->type = type;
+
+  switch ((guint)type)
+    {
+    case GTK_CSS_TOKEN_STRING:
+    case GTK_CSS_TOKEN_IDENT:
+    case GTK_CSS_TOKEN_FUNCTION:
+    case GTK_CSS_TOKEN_AT_KEYWORD:
+    case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
+    case GTK_CSS_TOKEN_HASH_ID:
+    case GTK_CSS_TOKEN_URL:
+      token->string.string = string;
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+gtk_css_token_init_delim (GtkCssToken *token,
+                          gunichar     delim)
+{
+  token->type = GTK_CSS_TOKEN_DELIM;
+  token->delim.delim = delim;
+}
+
+static void
+gtk_css_token_init_number (GtkCssToken     *token,
+                           GtkCssTokenType  type,
+                           double           value)
+{
+  token->type = type;
+
+  switch ((guint)type)
+    {
+    case GTK_CSS_TOKEN_SIGNED_INTEGER:
+    case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
+    case GTK_CSS_TOKEN_SIGNED_NUMBER:
+    case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
+    case GTK_CSS_TOKEN_PERCENTAGE:
+      token->number.number = value;
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+gtk_css_token_init_dimension (GtkCssToken     *token,
+                              GtkCssTokenType  type,
+                              double           value,
+                              char            *dimension)
+{
+  token->type = type;
+
+  switch ((guint)type)
+    {
+    case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNED_DIMENSION:
+    case GTK_CSS_TOKEN_SIGNLESS_DIMENSION:
+      token->dimension.value = value;
+      token->dimension.dimension = dimension;
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+GtkCssTokenizer *
+gtk_css_tokenizer_new (GBytes *bytes)
+{
+  GtkCssTokenizer *tokenizer;
+
+  tokenizer = g_slice_new0 (GtkCssTokenizer);
+  tokenizer->ref_count = 1;
+  tokenizer->bytes = g_bytes_ref (bytes);
+  tokenizer->name_buffer = g_string_new (NULL);
+
+  tokenizer->data = g_bytes_get_data (bytes, NULL);
+  tokenizer->end = tokenizer->data + g_bytes_get_size (bytes);
+
+  gtk_css_location_init (&tokenizer->position);
+
+  return tokenizer;
+}
+
+GtkCssTokenizer *
+gtk_css_tokenizer_ref (GtkCssTokenizer *tokenizer)
+{
+  tokenizer->ref_count++;
+  
+  return tokenizer;
+}
+
+void
+gtk_css_tokenizer_unref (GtkCssTokenizer *tokenizer)
+{
+  tokenizer->ref_count--;
+  if (tokenizer->ref_count > 0)
+    return;
+
+  g_string_free (tokenizer->name_buffer, TRUE);
+  g_bytes_unref (tokenizer->bytes);
+  g_slice_free (GtkCssTokenizer, tokenizer);
+}
+
+const GtkCssLocation *
+gtk_css_tokenizer_get_location (GtkCssTokenizer *tokenizer)
+{
+  return &tokenizer->position;
+}
+
+static void G_GNUC_PRINTF(2, 3)
+gtk_css_tokenizer_parse_error (GError     **error,
+                               const char  *format,
+                               ...)
+{
+  va_list args;
+
+  va_start (args, format);
+  if (error)
+    {
+      *error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+                                   GTK_CSS_PARSER_ERROR_SYNTAX,
+                                   format, args);
+    }
+  else
+    {
+      char *s = g_strdup_vprintf (format, args);
+      g_print ("error: %s\n", s);
+      g_free (s);
+    }
+  va_end (args);
+}
+
+static gboolean
+is_newline (char c)
+{
+  return c == '\n'
+      || c == '\r'
+      || c == '\f';
+}
+
+static gboolean
+is_whitespace (char c)
+{
+  return is_newline (c)
+      || c == '\t'
+      || c == ' ';
+}
+
+static gboolean
+is_multibyte (char c)
+{
+  return c & 0x80;
+}
+
+static gboolean
+is_name_start (char c)
+{
+   return is_multibyte (c)
+       || g_ascii_isalpha (c)
+       || c == '_';
+}
+
+static gboolean
+is_name (char c)
+{
+  return is_name_start (c)
+      || g_ascii_isdigit (c)
+      || c == '-';
+}
+
+static gboolean
+is_non_printable (char c)
+{
+  return (c >= 0 && c <= 0x08)
+      || c == 0x0B
+      || c == 0x0E
+      || c == 0x1F
+      || c == 0x7F;
+}
+
+static gboolean
+is_valid_escape (const char *data,
+                 const char *end)
+{
+  switch (end - data)
+    {
+      default:
+        if (is_newline (data[1]))
+          return FALSE;
+        G_GNUC_FALLTHROUGH;
+
+      case 1:
+        return data[0] == '\\';
+
+      case 0:
+        return FALSE;
+    }
+}
+
+static inline gsize
+gtk_css_tokenizer_remaining (GtkCssTokenizer *tokenizer)
+{
+  return tokenizer->end - tokenizer->data;
+}
+
+static gboolean
+gtk_css_tokenizer_has_valid_escape (GtkCssTokenizer *tokenizer)
+{
+  return is_valid_escape (tokenizer->data, tokenizer->end);
+}
+
+static gboolean
+gtk_css_tokenizer_has_identifier (GtkCssTokenizer *tokenizer)
+{
+  const char *data = tokenizer->data;
+
+  if (data == tokenizer->end)
+    return FALSE;
+
+  if (*data == '-')
+    {
+      data++;
+      if (data == tokenizer->end)
+        return FALSE;
+      if (*data == '-')
+        return TRUE;
+    }
+
+  if (is_name_start (*data))
+    return TRUE;
+
+  if (*data == '\\')
+    {
+      data++;
+      if (data == tokenizer->end)
+        return TRUE; /* really? */
+      if (is_newline (*data))
+        return FALSE;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+gtk_css_tokenizer_has_number (GtkCssTokenizer *tokenizer)
+{
+  const char *data = tokenizer->data;
+
+  if (data == tokenizer->end)
+    return FALSE;
+
+  if (*data == '-' || *data == '+')
+    {
+      data++;
+      if (data == tokenizer->end)
+        return FALSE;
+    }
+
+  if (*data == '.')
+    {
+      data++;
+      if (data == tokenizer->end)
+        return FALSE;
+    }
+
+  return g_ascii_isdigit (*data);
+}
+
+static void
+gtk_css_tokenizer_consume_newline (GtkCssTokenizer *tokenizer)
+{
+  gsize n;
+
+  if (gtk_css_tokenizer_remaining (tokenizer) > 1 &&
+      tokenizer->data[0] == '\r' && tokenizer->data[1] == '\n')
+    n = 2;
+  else
+    n = 1;
+  
+  tokenizer->data += n;
+  gtk_css_location_advance_newline (&tokenizer->position, n == 2 ? TRUE : FALSE);
+}
+
+static inline void
+gtk_css_tokenizer_consume (GtkCssTokenizer *tokenizer,
+                           gsize            n_bytes,
+                           gsize            n_characters)
+{
+  /* NB: must not contain newlines! */
+  tokenizer->data += n_bytes;
+
+  gtk_css_location_advance (&tokenizer->position, n_bytes, n_characters);
+}
+
+static inline void
+gtk_css_tokenizer_consume_ascii (GtkCssTokenizer *tokenizer)
+{
+  /* NB: must not contain newlines! */
+  gtk_css_tokenizer_consume (tokenizer, 1, 1);
+}
+
+static inline void
+gtk_css_tokenizer_consume_whitespace (GtkCssTokenizer *tokenizer)
+{
+  if (is_newline (*tokenizer->data))
+    gtk_css_tokenizer_consume_newline (tokenizer);
+  else
+    gtk_css_tokenizer_consume_ascii (tokenizer);
+}
+
+static inline void
+gtk_css_tokenizer_consume_char (GtkCssTokenizer *tokenizer,
+                                GString         *string)
+{
+  if (is_newline (*tokenizer->data))
+    gtk_css_tokenizer_consume_newline (tokenizer);
+  else
+    {
+      gsize char_size = g_utf8_next_char (tokenizer->data) - tokenizer->data;
+
+      if (string)
+        g_string_append_len (string, tokenizer->data, char_size);
+      gtk_css_tokenizer_consume (tokenizer, char_size, 1);
+    }
+}
+
+static void
+gtk_css_tokenizer_read_whitespace (GtkCssTokenizer *tokenizer,
+                                   GtkCssToken     *token)
+{
+  do {
+    gtk_css_tokenizer_consume_whitespace (tokenizer);
+  } while (tokenizer->data != tokenizer->end &&
+           is_whitespace (*tokenizer->data));
+
+  gtk_css_token_init (token, GTK_CSS_TOKEN_WHITESPACE);
+}
+
+static gunichar 
+gtk_css_tokenizer_read_escape (GtkCssTokenizer *tokenizer)
+{
+  gunichar value = 0;
+  guint i;
+
+  gtk_css_tokenizer_consume (tokenizer, 1, 1);
+
+  for (i = 0; i < 6 && tokenizer->data < tokenizer->end && g_ascii_isxdigit (*tokenizer->data); i++)
+    {
+      value = value * 16 + g_ascii_xdigit_value (*tokenizer->data);
+      gtk_css_tokenizer_consume (tokenizer, 1, 1);
+    }
+
+  if (i == 0)
+    {
+      gsize remaining = gtk_css_tokenizer_remaining (tokenizer);
+      if (remaining == 0)
+        return 0xFFFD;
+
+      value = g_utf8_get_char_validated (tokenizer->data, remaining);
+      if (value == (gunichar) -1 || value == (gunichar) -2)
+        value = 0;
+
+      gtk_css_tokenizer_consume_char (tokenizer, NULL);
+    }
+  else
+    {
+      if (is_whitespace (*tokenizer->data))
+        gtk_css_tokenizer_consume_ascii (tokenizer);
+    }
+
+  if (!g_unichar_validate (value) || g_unichar_type (value) == G_UNICODE_SURROGATE)
+    return 0xFFFD;
+
+  return value;
+}
+
+static char *
+gtk_css_tokenizer_read_name (GtkCssTokenizer *tokenizer)
+{
+  g_string_set_size (tokenizer->name_buffer, 0);
+
+  do {
+      if (*tokenizer->data == '\\')
+        {
+          if (gtk_css_tokenizer_has_valid_escape (tokenizer))
+            {
+              gunichar value = gtk_css_tokenizer_read_escape (tokenizer);
+              g_string_append_unichar (tokenizer->name_buffer, value);
+            }
+          else
+            {
+              gtk_css_tokenizer_consume_ascii (tokenizer);
+
+              if (tokenizer->data == tokenizer->end)
+                {
+                  g_string_append_unichar (tokenizer->name_buffer, 0xFFFD);
+                  break;
+                }
+
+              gtk_css_tokenizer_consume_char (tokenizer, tokenizer->name_buffer);
+            }
+        }
+      else if (is_name (*tokenizer->data))
+        {
+          gtk_css_tokenizer_consume_char (tokenizer, tokenizer->name_buffer);
+        }
+      else
+        {
+          break;
+        }
+    }
+  while (tokenizer->data != tokenizer->end);
+
+  return g_strndup (tokenizer->name_buffer->str, tokenizer->name_buffer->len);
+}
+
+static void
+gtk_css_tokenizer_read_bad_url (GtkCssTokenizer  *tokenizer,
+                                GtkCssToken      *token)
+{
+  while (tokenizer->data < tokenizer->end && *tokenizer->data != ')')
+    {
+      if (gtk_css_tokenizer_has_valid_escape (tokenizer))
+        gtk_css_tokenizer_read_escape (tokenizer);
+      else
+        gtk_css_tokenizer_consume_char (tokenizer, NULL);
+    }
+  
+  if (tokenizer->data < tokenizer->end)
+    gtk_css_tokenizer_consume_ascii (tokenizer);
+
+  gtk_css_token_init (token, GTK_CSS_TOKEN_BAD_URL);
+}
+
+static gboolean
+gtk_css_tokenizer_read_url (GtkCssTokenizer  *tokenizer,
+                            GtkCssToken      *token,
+                            GError          **error)
+{
+  GString *url = g_string_new (NULL);
+
+  while (tokenizer->data < tokenizer->end && is_whitespace (*tokenizer->data))
+    gtk_css_tokenizer_consume_whitespace (tokenizer);
+
+  while (tokenizer->data < tokenizer->end)
+    {
+      if (*tokenizer->data == ')')
+        {
+          gtk_css_tokenizer_consume_ascii (tokenizer);
+          break;
+        }
+      else if (is_whitespace (*tokenizer->data))
+        {
+          do
+            gtk_css_tokenizer_consume_whitespace (tokenizer);
+          while (tokenizer->data < tokenizer->end && is_whitespace (*tokenizer->data));
+          
+          if (*tokenizer->data == ')')
+            {
+              gtk_css_tokenizer_consume_ascii (tokenizer);
+              break;
+            }
+          else if (tokenizer->data >= tokenizer->end)
+            {
+              break;
+            }
+          else
+            {
+              gtk_css_tokenizer_read_bad_url (tokenizer, token);
+              gtk_css_tokenizer_parse_error (error, "Whitespace only allowed at start and end of url");
+              return FALSE;
+            }
+        }
+      else if (is_non_printable (*tokenizer->data))
+        {
+          gtk_css_tokenizer_read_bad_url (tokenizer, token);
+          g_string_free (url, TRUE);
+          gtk_css_tokenizer_parse_error (error, "Nonprintable character 0x%02X in url", *tokenizer->data);
+          return FALSE;
+        }
+      else if (*tokenizer->data == '"' ||
+               *tokenizer->data == '\'' ||
+               *tokenizer->data == '(')
+        {
+          gtk_css_tokenizer_read_bad_url (tokenizer, token);
+          gtk_css_tokenizer_parse_error (error, "Invalid character %c in url", *tokenizer->data);
+          g_string_free (url, TRUE);
+          return FALSE;
+        }
+      else if (gtk_css_tokenizer_has_valid_escape (tokenizer))
+        {
+          g_string_append_unichar (url, gtk_css_tokenizer_read_escape (tokenizer));
+        }
+      else if (*tokenizer->data == '\\')
+        {
+          gtk_css_tokenizer_read_bad_url (tokenizer, token);
+          gtk_css_tokenizer_parse_error (error, "Newline may not follow '\' escape character");
+          g_string_free (url, TRUE);
+          return FALSE;
+        }
+      else
+        {
+          gtk_css_tokenizer_consume_char (tokenizer, url);
+        }
+    }
+
+  gtk_css_token_init_string (token, GTK_CSS_TOKEN_URL, g_string_free (url, FALSE));
+
+  return TRUE;
+}
+
+static gboolean
+gtk_css_tokenizer_read_ident_like (GtkCssTokenizer  *tokenizer,
+                                   GtkCssToken      *token,
+                                   GError          **error)
+{
+  char *name = gtk_css_tokenizer_read_name (tokenizer);
+
+  if (*tokenizer->data == '(')
+    {
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      if (g_ascii_strcasecmp (name, "url") == 0)
+        {
+          const char *data = tokenizer->data;
+
+          while (is_whitespace (*data))
+            data++;
+
+          if (*data != '"' && *data != '\'')
+            {
+              g_free (name);
+              return gtk_css_tokenizer_read_url (tokenizer, token, error);
+            }
+        }
+
+      gtk_css_token_init_string (token, GTK_CSS_TOKEN_FUNCTION, name);
+      return TRUE;
+    }
+  else
+    {
+      gtk_css_token_init_string (token, GTK_CSS_TOKEN_IDENT, name);
+      return TRUE;
+    }
+}
+
+static void
+gtk_css_tokenizer_read_numeric (GtkCssTokenizer *tokenizer,
+                                GtkCssToken     *token)
+{
+  int sign = 1, exponent_sign = 1;
+  gint64 integer, fractional = 0, fractional_length = 1, exponent = 0;
+  gboolean is_int = TRUE, has_sign = FALSE;
+  const char *data = tokenizer->data;
+  double value;
+
+  if (*data == '-')
+    {
+      has_sign = TRUE;
+      sign = -1;
+      data++;
+    }
+  else if (*data == '+')
+    {
+      has_sign = TRUE;
+      data++;
+    }
+
+  for (integer = 0; data < tokenizer->end && g_ascii_isdigit (*data); data++)
+    {
+      /* check for overflow here? */
+      integer = 10 * integer + g_ascii_digit_value (*data);
+    }
+
+  if (data + 1 < tokenizer->end && *data == '.' && g_ascii_isdigit (data[1]))
+    {
+      is_int = FALSE;
+      data++;
+
+      fractional = g_ascii_digit_value (*data);
+      fractional_length = 10;
+      data++;
+
+      while (data < tokenizer->end && g_ascii_isdigit (*data))
+        {
+          if (fractional_length < G_MAXINT64 / 10)
+            {
+              fractional = 10 * fractional + g_ascii_digit_value (*data);
+              fractional_length *= 10;
+            }
+          data++;
+        }
+    }
+
+  if (data + 1 < tokenizer->end && (*data == 'e' || *data == 'E') &&
+      (g_ascii_isdigit (data[1]) ||
+       (data + 2 < tokenizer->end && (data[1] == '+' || data[1] == '-') && g_ascii_isdigit (data[2]))))
+    {
+      is_int = FALSE;
+      data++;
+
+      if (*data == '-')
+        {
+          exponent_sign = -1;
+          data++;
+        }
+      else if (*data == '+')
+        {
+          data++;
+        }
+
+      while (data < tokenizer->end && g_ascii_isdigit (*data))
+        {
+          exponent = 10 * exponent + g_ascii_digit_value (*data);
+          data++;
+        }
+    }
+
+  gtk_css_tokenizer_consume (tokenizer, data - tokenizer->data, data - tokenizer->data);
+
+  value = sign * (integer + ((double) fractional / fractional_length)) * pow (10, exponent_sign * exponent);
+
+  if (gtk_css_tokenizer_has_identifier (tokenizer))
+    {
+      GtkCssTokenType type;
+
+      if (is_int)
+        type = has_sign ? GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION : GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION;
+      else
+        type = has_sign ? GTK_CSS_TOKEN_SIGNED_DIMENSION : GTK_CSS_TOKEN_SIGNLESS_DIMENSION;
+
+      gtk_css_token_init_dimension (token, type, value, gtk_css_tokenizer_read_name (tokenizer));
+    }
+  else if (gtk_css_tokenizer_remaining (tokenizer) > 0 && *tokenizer->data == '%')
+    {
+      gtk_css_token_init_number (token, GTK_CSS_TOKEN_PERCENTAGE, value);
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+    }
+  else
+    {
+      GtkCssTokenType type;
+
+      if (is_int)
+        type = has_sign ? GTK_CSS_TOKEN_SIGNED_INTEGER : GTK_CSS_TOKEN_SIGNLESS_INTEGER;
+      else
+        type = has_sign ? GTK_CSS_TOKEN_SIGNED_NUMBER : GTK_CSS_TOKEN_SIGNLESS_NUMBER;
+
+      gtk_css_token_init_number (token, type,value);
+    }
+}
+
+static void
+gtk_css_tokenizer_read_delim (GtkCssTokenizer *tokenizer,
+                              GtkCssToken     *token)
+{
+  gtk_css_token_init_delim (token, g_utf8_get_char (tokenizer->data));
+  gtk_css_tokenizer_consume_char (tokenizer, NULL);
+}
+
+static gboolean
+gtk_css_tokenizer_read_dash (GtkCssTokenizer  *tokenizer,
+                             GtkCssToken      *token,
+                             GError          **error)
+{
+  if (gtk_css_tokenizer_remaining (tokenizer) == 1)
+    {
+      gtk_css_tokenizer_read_delim (tokenizer, token);
+      return TRUE;
+    }
+  else if (gtk_css_tokenizer_has_number (tokenizer))
+    {
+      gtk_css_tokenizer_read_numeric (tokenizer, token);
+      return TRUE;
+    }
+  else if (gtk_css_tokenizer_remaining (tokenizer) >= 3 &&
+           tokenizer->data[1] == '-' &&
+           tokenizer->data[2] == '>')
+    {
+      gtk_css_token_init (token, GTK_CSS_TOKEN_CDC);
+      gtk_css_tokenizer_consume (tokenizer, 3, 3);
+      return TRUE;
+    }
+  else if (gtk_css_tokenizer_has_identifier (tokenizer))
+    {
+      return gtk_css_tokenizer_read_ident_like (tokenizer, token, error);
+    }
+  else
+    {
+      gtk_css_tokenizer_read_delim (tokenizer, token);
+      return TRUE;
+    }
+}
+
+static gboolean
+gtk_css_tokenizer_read_string (GtkCssTokenizer  *tokenizer,
+                               GtkCssToken      *token,
+                               GError          **error)
+{
+  GString *string = g_string_new (NULL);
+  char end = *tokenizer->data;
+
+  gtk_css_tokenizer_consume_ascii (tokenizer);
+
+  while (tokenizer->data < tokenizer->end)
+    {
+      if (*tokenizer->data == end)
+        {
+          gtk_css_tokenizer_consume_ascii (tokenizer);
+          break;
+        }
+      else if (*tokenizer->data == '\\')
+        {
+          if (gtk_css_tokenizer_remaining (tokenizer) == 1)
+            {
+              gtk_css_tokenizer_consume_ascii (tokenizer);
+              break;
+            }
+          else if (is_newline (tokenizer->data[1]))
+            {
+              gtk_css_tokenizer_consume_ascii (tokenizer);
+              gtk_css_tokenizer_consume_newline (tokenizer);
+            }
+          else
+            {
+              g_string_append_unichar (string, gtk_css_tokenizer_read_escape (tokenizer));
+            }
+        }
+      else if (is_newline (*tokenizer->data))
+        {
+          g_string_free (string, TRUE);
+          gtk_css_token_init (token, GTK_CSS_TOKEN_BAD_STRING);
+          gtk_css_tokenizer_parse_error (error, "Newlines inside strings must be escaped");
+          return FALSE;
+        }
+      else
+        {
+          gtk_css_tokenizer_consume_char (tokenizer, string);
+        }
+    }
+
+  gtk_css_token_init_string (token, GTK_CSS_TOKEN_STRING, g_string_free (string, FALSE));
+
+  return TRUE;
+}
+
+static gboolean
+gtk_css_tokenizer_read_comment (GtkCssTokenizer  *tokenizer,
+                                GtkCssToken      *token,
+                                GError          **error)
+{
+  gtk_css_tokenizer_consume (tokenizer, 2, 2);
+
+  while (tokenizer->data < tokenizer->end)
+    {
+      if (gtk_css_tokenizer_remaining (tokenizer) > 1 &&
+          tokenizer->data[0] == '*' && tokenizer->data[1] == '/')
+        {
+          gtk_css_tokenizer_consume (tokenizer, 2, 2);
+          gtk_css_token_init (token, GTK_CSS_TOKEN_COMMENT);
+          return TRUE;
+        }
+      gtk_css_tokenizer_consume_char (tokenizer, NULL);
+    }
+
+  gtk_css_token_init (token, GTK_CSS_TOKEN_COMMENT);
+  gtk_css_tokenizer_parse_error (error, "Comment not terminated at end of document.");
+  return FALSE;
+}
+
+static void
+gtk_css_tokenizer_read_match (GtkCssTokenizer *tokenizer,
+                              GtkCssToken     *token,
+                              GtkCssTokenType  type)
+{
+  if (gtk_css_tokenizer_remaining (tokenizer) > 1 && tokenizer->data[1] == '=')
+    {
+      gtk_css_token_init (token, type);
+      gtk_css_tokenizer_consume (tokenizer, 2, 2);
+    }
+  else
+    {
+      gtk_css_tokenizer_read_delim (tokenizer, token);
+    }
+}
+
+gboolean
+gtk_css_tokenizer_read_token (GtkCssTokenizer  *tokenizer,
+                              GtkCssToken      *token,
+                              GError          **error)
+{
+  if (tokenizer->data == tokenizer->end)
+    {
+      gtk_css_token_init (token, GTK_CSS_TOKEN_EOF);
+      return TRUE;
+    }
+
+  if (tokenizer->data[0] == '/' && gtk_css_tokenizer_remaining (tokenizer) > 1 &&
+      tokenizer->data[1] == '*')
+    return gtk_css_tokenizer_read_comment (tokenizer, token, error);
+
+  switch (*tokenizer->data)
+    {
+    case '\n':
+    case '\r':
+    case '\t':
+    case '\f':
+    case ' ':
+      gtk_css_tokenizer_read_whitespace (tokenizer, token);
+      return TRUE;
+
+    case '"':
+      return gtk_css_tokenizer_read_string (tokenizer, token, error);
+
+    case '#':
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      if (is_name (*tokenizer->data) || gtk_css_tokenizer_has_valid_escape (tokenizer))
+        {
+          GtkCssTokenType type;
+
+          if (gtk_css_tokenizer_has_identifier (tokenizer))
+            type = GTK_CSS_TOKEN_HASH_ID;
+          else
+            type = GTK_CSS_TOKEN_HASH_UNRESTRICTED;
+
+          gtk_css_token_init_string (token,
+                                     type,
+                                     gtk_css_tokenizer_read_name (tokenizer));
+        }
+      else
+        {
+          gtk_css_token_init_delim (token, '#');
+        }
+      return TRUE;
+
+    case '$':
+      gtk_css_tokenizer_read_match (tokenizer, token, GTK_CSS_TOKEN_SUFFIX_MATCH);
+      return TRUE;
+
+    case '\'':
+      return gtk_css_tokenizer_read_string (tokenizer, token, error);
+
+    case '(':
+      gtk_css_token_init (token, GTK_CSS_TOKEN_OPEN_PARENS);
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      return TRUE;
+
+    case ')':
+      gtk_css_token_init (token, GTK_CSS_TOKEN_CLOSE_PARENS);
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      return TRUE;
+
+    case '*':
+      gtk_css_tokenizer_read_match (tokenizer, token, GTK_CSS_TOKEN_SUBSTRING_MATCH);
+      return TRUE;
+
+    case '+':
+      if (gtk_css_tokenizer_has_number (tokenizer))
+        gtk_css_tokenizer_read_numeric (tokenizer, token);
+      else
+        gtk_css_tokenizer_read_delim (tokenizer, token);
+      return TRUE;
+
+    case ',':
+      gtk_css_token_init (token, GTK_CSS_TOKEN_COMMA);
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      return TRUE;
+
+    case '-':
+      return gtk_css_tokenizer_read_dash (tokenizer, token, error);
+
+    case '.':
+      if (gtk_css_tokenizer_has_number (tokenizer))
+        gtk_css_tokenizer_read_numeric (tokenizer, token);
+      else
+        gtk_css_tokenizer_read_delim (tokenizer, token);
+      return TRUE;
+
+    case ':':
+      gtk_css_token_init (token, GTK_CSS_TOKEN_COLON);
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      return TRUE;
+
+    case ';':
+      gtk_css_token_init (token, GTK_CSS_TOKEN_SEMICOLON);
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      return TRUE;
+
+    case '<':
+      if (gtk_css_tokenizer_remaining (tokenizer) >= 4 &&
+          tokenizer->data[1] == '!' &&
+          tokenizer->data[2] == '-' &&
+          tokenizer->data[3] == '-')
+        {
+          gtk_css_token_init (token, GTK_CSS_TOKEN_CDO);
+          gtk_css_tokenizer_consume (tokenizer, 4, 4);
+        }
+      else
+        {
+          gtk_css_tokenizer_read_delim (tokenizer, token);
+        }
+      return TRUE;
+
+    case '@':
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      if (gtk_css_tokenizer_has_identifier (tokenizer))
+        {
+          gtk_css_token_init_string (token,
+                                     GTK_CSS_TOKEN_AT_KEYWORD,
+                                     gtk_css_tokenizer_read_name (tokenizer));
+        }
+      else
+        {
+          gtk_css_token_init_delim (token, '@');
+        }
+      return TRUE;
+
+    case '[':
+      gtk_css_token_init (token, GTK_CSS_TOKEN_OPEN_SQUARE);
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      return TRUE;
+
+    case '\\':
+      if (gtk_css_tokenizer_has_valid_escape (tokenizer))
+        {
+          return gtk_css_tokenizer_read_ident_like (tokenizer, token, error);
+        }
+      else
+        {
+          gtk_css_token_init_delim (token, '\\');
+          gtk_css_tokenizer_consume_ascii (tokenizer);
+          gtk_css_tokenizer_parse_error (error, "Newline may not follow '\' escape character");
+          return FALSE;
+        }
+
+    case ']':
+      gtk_css_token_init (token, GTK_CSS_TOKEN_CLOSE_SQUARE);
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      return TRUE;
+
+    case '^':
+      gtk_css_tokenizer_read_match (tokenizer, token, GTK_CSS_TOKEN_PREFIX_MATCH);
+      return TRUE;
+
+    case '{':
+      gtk_css_token_init (token, GTK_CSS_TOKEN_OPEN_CURLY);
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      return TRUE;
+
+    case '}':
+      gtk_css_token_init (token, GTK_CSS_TOKEN_CLOSE_CURLY);
+      gtk_css_tokenizer_consume_ascii (tokenizer);
+      return TRUE;
+
+    case '|':
+      if (gtk_css_tokenizer_remaining (tokenizer) > 1 && tokenizer->data[1] == '|')
+        {
+          gtk_css_token_init (token, GTK_CSS_TOKEN_COLUMN);
+          gtk_css_tokenizer_consume (tokenizer, 2, 2);
+        }
+      else
+        {
+          gtk_css_tokenizer_read_match (tokenizer, token, GTK_CSS_TOKEN_DASH_MATCH);
+        }
+      return TRUE;
+
+    case '~':
+      gtk_css_tokenizer_read_match (tokenizer, token, GTK_CSS_TOKEN_INCLUDE_MATCH);
+      return TRUE;
+
+    default:
+      if (g_ascii_isdigit (*tokenizer->data))
+        {
+          gtk_css_tokenizer_read_numeric (tokenizer, token);
+          return TRUE;
+        }
+      else if (is_name_start (*tokenizer->data))
+        {
+          return gtk_css_tokenizer_read_ident_like (tokenizer, token, error);
+        }
+      else
+        {
+          gtk_css_tokenizer_read_delim (tokenizer, token);
+          return TRUE;
+        }
+    }
+}
+
diff --git a/pango/css/gtkcsstokenizerprivate.h b/pango/css/gtkcsstokenizerprivate.h
new file mode 100644
index 00000000..c490d2a0
--- /dev/null
+++ b/pango/css/gtkcsstokenizerprivate.h
@@ -0,0 +1,141 @@
+/* GSK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_CSS_TOKENIZER_PRIVATE_H__
+#define __GTK_CSS_TOKENIZER_PRIVATE_H__
+
+#include <glib.h>
+
+#include <pango/css/gtkcsslocation.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+  /* no content */
+  GTK_CSS_TOKEN_EOF,
+  GTK_CSS_TOKEN_WHITESPACE,
+  GTK_CSS_TOKEN_OPEN_PARENS,
+  GTK_CSS_TOKEN_CLOSE_PARENS,
+  GTK_CSS_TOKEN_OPEN_SQUARE,
+  GTK_CSS_TOKEN_CLOSE_SQUARE,
+  GTK_CSS_TOKEN_OPEN_CURLY,
+  GTK_CSS_TOKEN_CLOSE_CURLY,
+  GTK_CSS_TOKEN_COMMA,
+  GTK_CSS_TOKEN_COLON,
+  GTK_CSS_TOKEN_SEMICOLON,
+  GTK_CSS_TOKEN_CDO,
+  GTK_CSS_TOKEN_CDC,
+  GTK_CSS_TOKEN_INCLUDE_MATCH,
+  GTK_CSS_TOKEN_DASH_MATCH,
+  GTK_CSS_TOKEN_PREFIX_MATCH,
+  GTK_CSS_TOKEN_SUFFIX_MATCH,
+  GTK_CSS_TOKEN_SUBSTRING_MATCH,
+  GTK_CSS_TOKEN_COLUMN,
+  GTK_CSS_TOKEN_BAD_STRING,
+  GTK_CSS_TOKEN_BAD_URL,
+  GTK_CSS_TOKEN_COMMENT,
+  /* delim */
+  GTK_CSS_TOKEN_DELIM,
+  /* string */
+  GTK_CSS_TOKEN_STRING,
+  GTK_CSS_TOKEN_IDENT,
+  GTK_CSS_TOKEN_FUNCTION,
+  GTK_CSS_TOKEN_AT_KEYWORD,
+  GTK_CSS_TOKEN_HASH_UNRESTRICTED,
+  GTK_CSS_TOKEN_HASH_ID,
+  GTK_CSS_TOKEN_URL,
+  /* number */
+  GTK_CSS_TOKEN_SIGNED_INTEGER,
+  GTK_CSS_TOKEN_SIGNLESS_INTEGER,
+  GTK_CSS_TOKEN_SIGNED_NUMBER,
+  GTK_CSS_TOKEN_SIGNLESS_NUMBER,
+  GTK_CSS_TOKEN_PERCENTAGE,
+  /* dimension */
+  GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION,
+  GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION,
+  GTK_CSS_TOKEN_SIGNED_DIMENSION,
+  GTK_CSS_TOKEN_SIGNLESS_DIMENSION
+} GtkCssTokenType;
+
+typedef union _GtkCssToken GtkCssToken;
+typedef struct _GtkCssTokenizer GtkCssTokenizer;
+
+typedef struct _GtkCssStringToken GtkCssStringToken;
+typedef struct _GtkCssDelimToken GtkCssDelimToken;
+typedef struct _GtkCssNumberToken GtkCssNumberToken;
+typedef struct _GtkCssDimensionToken GtkCssDimensionToken;
+
+struct _GtkCssStringToken {
+  GtkCssTokenType  type;
+  char            *string;
+};
+
+struct _GtkCssDelimToken {
+  GtkCssTokenType  type;
+  gunichar         delim;
+};
+
+struct _GtkCssNumberToken {
+  GtkCssTokenType  type;
+  double           number;
+};
+
+struct _GtkCssDimensionToken {
+  GtkCssTokenType  type;
+  double           value;
+  char            *dimension;
+};
+
+union _GtkCssToken {
+  GtkCssTokenType type;
+  GtkCssStringToken string;
+  GtkCssDelimToken delim;
+  GtkCssNumberToken number;
+  GtkCssDimensionToken dimension;
+};
+
+void                    gtk_css_token_clear                     (GtkCssToken            *token);
+
+gboolean                gtk_css_token_is_finite                 (const GtkCssToken      *token) G_GNUC_PURE;
+gboolean                gtk_css_token_is_preserved              (const GtkCssToken      *token,
+                                                                 GtkCssTokenType        *out_closing) 
G_GNUC_PURE;
+#define gtk_css_token_is(token, _type) ((token)->type == (_type))
+gboolean                gtk_css_token_is_ident                  (const GtkCssToken      *token,
+                                                                 const char             *ident) G_GNUC_PURE;
+gboolean                gtk_css_token_is_function               (const GtkCssToken      *token,
+                                                                 const char             *ident) G_GNUC_PURE;
+gboolean                gtk_css_token_is_delim                  (const GtkCssToken      *token,
+                                                                 gunichar                delim) G_GNUC_PURE;
+
+void                    gtk_css_token_print                     (const GtkCssToken      *token,
+                                                                 GString                *string);
+char *                  gtk_css_token_to_string                 (const GtkCssToken      *token);
+
+GtkCssTokenizer *       gtk_css_tokenizer_new                   (GBytes                 *bytes);
+
+GtkCssTokenizer *       gtk_css_tokenizer_ref                   (GtkCssTokenizer        *tokenizer);
+void                    gtk_css_tokenizer_unref                 (GtkCssTokenizer        *tokenizer);
+
+const GtkCssLocation *  gtk_css_tokenizer_get_location          (GtkCssTokenizer        *tokenizer) 
G_GNUC_CONST;
+
+gboolean                gtk_css_tokenizer_read_token            (GtkCssTokenizer        *tokenizer,
+                                                                 GtkCssToken            *token,
+                                                                 GError                **error);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_TOKENIZER_PRIVATE_H__ */
diff --git a/pango/css/meson.build b/pango/css/meson.build
new file mode 100644
index 00000000..353724e7
--- /dev/null
+++ b/pango/css/meson.build
@@ -0,0 +1,54 @@
+gtk_css_public_sources = files([
+  'gtkcsslocation.c',
+  'gtkcsserror.c',
+  'gtkcsssection.c',
+])
+
+gtk_css_private_sources = files([
+  'gtkcssdataurl.c',
+  'gtkcssparser.c',
+  'gtkcsstokenizer.c',
+  'gtkcssserializer.c',
+])
+
+gtk_css_public_headers = files([
+  'gtkcssenums.h',
+  'gtkcsserror.h',
+  'gtkcsslocation.h',
+  'gtkcsssection.h',
+])
+
+gtk_css_deps = [
+  libm,
+  glib_dep,
+  gobject_dep,
+  platform_gio_dep,
+]
+
+gtk_css_enums = gnome.mkenums('gtkcssenumtypes',
+  sources: gtk_css_public_headers,
+  c_template: 'gtkcssenumtypes.c.template',
+  h_template: 'gtkcssenumtypes.h.template',
+)
+
+gtk_css_enum_h = gtk_css_enums[1]
+
+libgtk_css = static_library('gtk_css',
+  sources: [
+    gtk_css_public_sources,
+    gtk_css_private_sources,
+    gtk_css_enums,
+  ],
+  dependencies: gtk_css_deps,
+  include_directories: [ confinc, ],
+  c_args: [
+    '-DGTK_COMPILATION',
+    '-DG_LOG_DOMAIN="Gtk"',
+  ] + common_cflags,
+)
+
+libgtk_css_dep = declare_dependency(include_directories: [ confinc, ],
+  sources: [ gtk_css_enum_h ],
+  dependencies: gtk_css_deps,
+  link_with: libgtk_css,
+)
diff --git a/pango/meson.build b/pango/meson.build
index 7c7bb280..dfa1720b 100644
--- a/pango/meson.build
+++ b/pango/meson.build
@@ -1,3 +1,35 @@
+# Maths functions might be implemented in libm
+libm = cc.find_library('m', required: false)
+
+glib_req = glib_req_version
+confinc = root_inc
+os_unix   = false
+os_win32  = false
+
+if host_machine.system() == 'windows'
+  os_win32 = true
+else
+  os_unix = true
+endif
+
+if os_unix
+  giounix_dep  = dependency('gio-unix-2.0', version: glib_req, required: false,
+                            fallback : ['glib', 'libgio_dep'])
+endif
+if os_win32
+  giowin32_dep = dependency('gio-windows-2.0', version: glib_req, required: win32_enabled,
+                            fallback : ['glib', 'libgio_dep'])
+endif
+
+if os_win32
+  platform_gio_dep = giowin32_dep
+endif
+if os_unix
+  platform_gio_dep = giounix_dep
+endif
+
+subdir('css')
+
 pango_sources = [
   'break.c',
   'ellipsize.c',
@@ -117,7 +149,7 @@ libpango = library(
   soversion: pango_soversion,
   darwin_versions : pango_osxversion,
   install: true,
-  dependencies: pango_deps,
+  dependencies: [pango_deps, libgtk_css_dep],
   include_directories: [ root_inc, pango_inc ],
   c_args: common_cflags + pango_debug_cflags + pango_cflags,
   link_args: common_ldflags,


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