+style_init()
+{
+ ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+ GHashTable *default_text_grid_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
+ GHashTable *default_text_buffer_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
+ GHashTable *current_text_grid_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
+ GHashTable *current_text_buffer_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
+ GtkTextTag *tag;
+
+ /* Initialise the default styles for a text grid */
+ tag = gtk_text_tag_new("normal");
+ g_object_set(tag, "font-desc", glk_data->monospace_font_desc, NULL);
+ g_hash_table_insert(default_text_grid_styles, "normal", tag);
+
+ tag = gtk_text_tag_new("emphasized");
+ g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
+ g_hash_table_insert(default_text_grid_styles, "emphasized", tag);
+
+ tag = gtk_text_tag_new("preformatted");
+ g_object_set(tag, "font-desc", glk_data->monospace_font_desc, NULL);
+ g_hash_table_insert(default_text_grid_styles, "preformatted", tag);
+
+ tag = gtk_text_tag_new("header");
+ g_object_set(tag, "weight", PANGO_WEIGHT_BOLD, NULL);
+ g_hash_table_insert(default_text_grid_styles, "header", tag);
+
+ tag = gtk_text_tag_new("subheader");
+ g_object_set(tag, "weight", PANGO_WEIGHT_BOLD, NULL);
+ g_hash_table_insert(default_text_grid_styles, "subheader", tag);
+
+ tag = gtk_text_tag_new("alert");
+ g_object_set(tag, "foreground", "#aa0000", "weight", PANGO_WEIGHT_BOLD, NULL);
+ g_hash_table_insert(default_text_grid_styles, "alert", tag);
+
+ tag = gtk_text_tag_new("note");
+ g_object_set(tag, "foreground", "#aaaa00", "weight", PANGO_WEIGHT_BOLD, NULL);
+ g_hash_table_insert(default_text_grid_styles, "note", tag);
+
+ tag = gtk_text_tag_new("block-quote");
+ g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
+ g_hash_table_insert(default_text_grid_styles, "block-quote", tag);
+
+ g_hash_table_insert(default_text_grid_styles, "input", gtk_text_tag_new("input"));
+ g_hash_table_insert(default_text_grid_styles, "user1", gtk_text_tag_new("user1"));
+ g_hash_table_insert(default_text_grid_styles, "user2", gtk_text_tag_new("user2"));
+
+ tag = gtk_text_tag_new("hyperlink");
+ g_object_set(tag, "foreground", "#0000ff", "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL);
+ g_hash_table_insert(default_text_grid_styles, "hyperlink", tag);
+
+ /* Tags for the textbuffer */
+ tag = gtk_text_tag_new("normal");
+ g_object_set(tag, "font-desc", glk_data->default_font_desc, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "normal", tag);
+
+ tag = gtk_text_tag_new("emphasized");
+ g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "emphasized", tag);
+
+ tag = gtk_text_tag_new("preformatted");
+ g_object_set(tag, "font-desc", glk_data->monospace_font_desc, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "preformatted", tag);
+
+ tag = gtk_text_tag_new("header");
+ g_object_set(tag, "size-points", 18.0, "weight", PANGO_WEIGHT_BOLD, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "header", tag);
+
+ tag = gtk_text_tag_new("subheader");
+ g_object_set(tag, "size-points", 14.0, "weight", PANGO_WEIGHT_BOLD, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "subheader", tag);
+
+ tag = gtk_text_tag_new("alert");
+ g_object_set(tag, "foreground", "#aa0000", "weight", PANGO_WEIGHT_BOLD, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "alert", tag);
+
+ tag = gtk_text_tag_new("note");
+ g_object_set(tag, "foreground", "#aaaa00", "weight", PANGO_WEIGHT_BOLD, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "note", tag);
+
+ tag = gtk_text_tag_new("block-quote");
+ g_object_set(tag, "justification", GTK_JUSTIFY_CENTER, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "block-quote", tag);
+
+ g_hash_table_insert(default_text_buffer_styles, "input", gtk_text_tag_new("input"));
+ g_hash_table_insert(default_text_buffer_styles, "user1", gtk_text_tag_new("user1"));
+ g_hash_table_insert(default_text_buffer_styles, "user2", gtk_text_tag_new("user2"));
+
+ tag = gtk_text_tag_new("hyperlink");
+ g_object_set(tag, "foreground", "#0000ff", "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "hyperlink", tag);
+
+ GtkTextTag *pager_tag = gtk_text_tag_new("pager");
+ g_object_set(pager_tag, "foreground", "#ffffff", "background", "#000000", NULL);
+ g_hash_table_insert(default_text_buffer_styles, "pager", pager_tag);
+
+ glk_data->default_styles->text_grid = default_text_grid_styles;
+ glk_data->default_styles->text_buffer = default_text_buffer_styles;
+
+ /* Create the CSS file scanner */
+ GScanner *scanner = g_scanner_new(NULL);
+
+ if(glk_data->css_file != NULL) {
+ int f = open(glk_data->css_file, O_RDONLY);
+ if(f != -1)
+ {
+ g_scanner_input_file(scanner, f);
+ scanner->input_name = glk_data->css_file;
+ scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z "#";
+ scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z "-_" G_CSET_DIGITS;
+ scanner->config->symbol_2_token = TRUE;
+ scanner->config->cpair_comment_single = NULL;
+ scanner->config->scan_float = FALSE;
+
+ /* Run the scanner over the CSS file, overriding defaults */
+ while( g_scanner_peek_next_token(scanner) != G_TOKEN_EOF) {
+ if( !style_accept_style_selector(scanner) )
+ break;
+ }
+
+ g_scanner_destroy(scanner);
+ }
+ else
+ g_warning("Could not find CSS file");
+ }
+
+ /* Set the current style to a copy of the default style */
+ g_hash_table_foreach(default_text_grid_styles, style_table_copy, current_text_grid_styles);
+ g_hash_table_foreach(default_text_buffer_styles, style_table_copy, current_text_buffer_styles);
+ glk_data->current_styles->text_grid = current_text_grid_styles;
+ glk_data->current_styles->text_buffer = current_text_buffer_styles;
+
+ text_tag_to_attr_list(pager_tag, glk_data->pager_attr_list);
+
+ glk_data->style_initialized = TRUE;
+}
+
+/* Internal function: parses a token */
+static gboolean
+style_accept(GScanner *scanner, GTokenType token)
+{
+ GTokenType next = g_scanner_get_next_token(scanner);
+ if(next != token ) {
+ g_scanner_unexp_token(scanner, token, NULL, NULL, NULL, "CSS Error", 1);
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+}
+
+/* Internal function: parses a style selector */
+static gboolean
+style_accept_style_selector(GScanner *scanner)
+{
+ ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+
+ GtkTextTag *current_tag;
+ gchar *field;
+ GTokenType token = g_scanner_get_next_token(scanner);
+ GTokenValue value = g_scanner_cur_value(scanner);
+
+ if(
+ token != G_TOKEN_IDENTIFIER ||
+ ( strcmp(value.v_identifier, "buffer") && strcmp(value.v_identifier, "grid") )
+ ) {
+ g_scanner_error(scanner, "CSS Error: buffer/grid expected");
+ return FALSE;
+ }
+
+ field = g_strdup(value.v_identifier);
+
+ if( !style_accept(scanner, '.') )
+ return FALSE;
+
+ token = g_scanner_get_next_token(scanner);
+ value = g_scanner_cur_value(scanner);
+
+ if(token != G_TOKEN_IDENTIFIER) {
+ g_scanner_error(scanner, "CSS Error: style selector expected");
+ return FALSE;
+ }
+
+ if( !strcmp(field, "buffer") )
+ current_tag = g_hash_table_lookup(glk_data->default_styles->text_buffer, value.v_identifier);
+ else
+ current_tag = g_hash_table_lookup(glk_data->default_styles->text_grid, value.v_identifier);
+
+ if(current_tag == NULL) {
+ g_scanner_error(scanner, "CSS Error: invalid style identifier");
+ return FALSE;
+ }
+
+ if( !style_accept(scanner, '{') )
+ return FALSE;
+
+ while( g_scanner_peek_next_token(scanner) != '}') {
+ if( !style_accept_style_hint(scanner, current_tag) )
+ return FALSE;
+ }
+
+ if( !style_accept(scanner, '}') )
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Internal function: parses a style hint */
+static gboolean
+style_accept_style_hint(GScanner *scanner, GtkTextTag *current_tag)