From cf06531bc15786a4f2a2292b532733ea41c4fdee Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 13 Nov 2021 11:01:27 -0500 Subject: 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(-) create mode 100644 pango/css/gtkcss.h create mode 100644 pango/css/gtkcssdataurl.c create mode 100644 pango/css/gtkcssdataurlprivate.h create mode 100644 pango/css/gtkcssenums.h create mode 100644 pango/css/gtkcssenumtypes.c.template create mode 100644 pango/css/gtkcssenumtypes.h.template create mode 100644 pango/css/gtkcsserror.c create mode 100644 pango/css/gtkcsserror.h create mode 100644 pango/css/gtkcsslocation.c create mode 100644 pango/css/gtkcsslocation.h create mode 100644 pango/css/gtkcsslocationprivate.h create mode 100644 pango/css/gtkcssparser.c create mode 100644 pango/css/gtkcssparserprivate.h create mode 100644 pango/css/gtkcsssection.c create mode 100644 pango/css/gtkcsssection.h create mode 100644 pango/css/gtkcssserializer.c create mode 100644 pango/css/gtkcssserializerprivate.h create mode 100644 pango/css/gtkcsstokenizer.c create mode 100644 pango/css/gtkcsstokenizerprivate.h create mode 100644 pango/css/meson.build 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 . + */ + +/* + * 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 + +#include +#include +#include +#include +#include + +#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 + * + * 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. + */ + +/* + * # 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 + +#define _(x) x + +/* + * 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:[][;base64], + */ + 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 . + * + * Authors: Benjamin Otte + */ + + +#ifndef __GTK_CSS_DATA_URL_PRIVATE_H__ +#define __GTK_CSS_DATA_URL_PRIVATE_H__ + +#include + +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 . + */ + +/* + * 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 + +/** + * 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 + +/*** 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 + +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 . + * + * Authors: Benjamin Otte + */ + +#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 . + * + * Authors: Benjamin Otte + */ + + +#ifndef __GTK_CSS_ERROR_H__ +#define __GTK_CSS_ERROR_H__ + +#include + +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 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "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 + * + * 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 . + */ + +#ifndef __GTK_CSS_LOCATION_H__ +#define __GTK_CSS_LOCATION_H__ + +#if !defined (__GTK_CSS_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +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 . + * + * Authors: Benjamin Otte + */ + + +#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 . + * + * Authors: Benjamin Otte + */ + + +#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 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 . + * + * Authors: Benjamin Otte + */ + + +#ifndef __GTK_CSS_PARSER_H__ +#define __GTK_CSS_PARSER_H__ + +#include "gtkcssenums.h" +#include "gtkcsstokenizerprivate.h" + +#include + +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 . + */ + +#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 §ion->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 §ion->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, ""); + } + } + else + { + g_string_append (string, ""); + } + + 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 . + */ + +#ifndef __GTK_CSS_SECTION_H__ +#define __GTK_CSS_SECTION_H__ + +#include +#include + +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 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "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 . + * + * Authors: Matthias Clasen + */ + + +#ifndef __GTK_CSS_SERIALIZER_H__ +#define __GTK_CSS_SERIALIZER_H__ + +#include + +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 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gtkcsstokenizerprivate.h" + +#include "gtkcssenums.h" +#include "gtkcsserror.h" +#include "gtkcsslocationprivate.h" + +#include +#include + +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_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 + * + * 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 . + */ + +#ifndef __GTK_CSS_TOKENIZER_PRIVATE_H__ +#define __GTK_CSS_TOKENIZER_PRIVATE_H__ + +#include + +#include + +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, -- cgit v1.2.1