+ CHIMARA_GLK_USE_PRIVATE(glk, priv);
+
+ 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;
+
+ PangoFontDescription *default_font_desc = pango_font_description_from_string("Serif");
+ PangoFontDescription *monospace_font_desc = pango_font_description_from_string("Monospace");
+
+ /* Initialise the default styles for a text grid */
+ tag = gtk_text_tag_new("normal");
+ g_object_set(tag, "font-desc", 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", 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", 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", 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);
+
+ pango_font_description_free(default_font_desc);
+ pango_font_description_free(monospace_font_desc);
+
+ priv->default_styles->text_grid = default_text_grid_styles;
+ priv->default_styles->text_buffer = default_text_buffer_styles;
+ priv->current_styles->text_grid = current_text_grid_styles;
+ priv->current_styles->text_buffer = current_text_buffer_styles;
+
+ /* Set the current style to a copy of the default style */
+ copy_default_styles_to_current_styles(glk);
+
+ text_tag_to_attr_list(pager_tag, priv->pager_attr_list);
+}
+
+/* Reset style tables to the library's internal defaults */
+void
+reset_default_styles(ChimaraGlk *glk)
+{
+ /* TODO: write this function */
+}
+
+/* Copy the default styles to the current styles
+ FIXME: This function is temporary and will not be needed later on */
+void
+copy_default_styles_to_current_styles(ChimaraGlk *glk)
+{
+ CHIMARA_GLK_USE_PRIVATE(glk, priv);
+ g_hash_table_foreach(priv->default_styles->text_grid, style_table_copy, priv->current_styles->text_grid);
+ g_hash_table_foreach(priv->default_styles->text_buffer, style_table_copy, priv->current_styles->text_buffer);
+}
+
+/* Create the CSS file scanner */
+GScanner *
+create_css_file_scanner(void)
+{
+ GScanner *scanner = g_scanner_new(NULL);
+ 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;
+ return scanner;
+}
+
+/* Run the scanner over the CSS file, overriding the default styles */
+void
+scan_css_file(GScanner *scanner, ChimaraGlk *glk)
+{
+ while( g_scanner_peek_next_token(scanner) != G_TOKEN_EOF) {
+ if( !style_accept_style_selector(scanner, glk) )
+ break;
+ }
+
+ g_scanner_destroy(scanner);
+}
+
+/* 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;
+ }
+ return TRUE;
+}
+
+/* Internal function: parses a style selector */
+static gboolean
+style_accept_style_selector(GScanner *scanner, ChimaraGlk *glk)
+{
+ CHIMARA_GLK_USE_PRIVATE(glk, priv);
+
+ 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(priv->default_styles->text_buffer, value.v_identifier);
+ else
+ current_tag = g_hash_table_lookup(priv->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)
+{
+ GTokenType token = g_scanner_get_next_token(scanner);
+ GTokenValue value = g_scanner_cur_value(scanner);
+ gchar *hint;
+
+ if(token != G_TOKEN_IDENTIFIER) {
+ g_scanner_error(scanner, "CSS Error: style hint expected");
+ return FALSE;
+ }
+
+ hint = 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( !strcmp(hint, "font-family") ) {
+ if(token != G_TOKEN_STRING) {
+ g_scanner_error(scanner, "CSS Error: string expected");
+ return FALSE;
+ }
+ g_object_set(current_tag, "family", value.v_string, "family-set", TRUE, NULL);
+ }
+ else if( !strcmp(hint, "font-weight") ) {
+ if(token != G_TOKEN_IDENTIFIER) {
+ g_scanner_error(scanner, "CSS Error: bold/normal expected");
+ return FALSE;
+ }
+
+ if( !strcmp(value.v_identifier, "bold") )
+ g_object_set(current_tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
+ else if( !strcmp(value.v_identifier, "normal") )
+ g_object_set(current_tag, "weight", PANGO_WEIGHT_NORMAL, "weight-set", TRUE, NULL);
+ else {
+ g_scanner_error(scanner, "CSS Error: bold/normal expected");
+ return FALSE;
+ }
+ }
+ else if( !strcmp(hint, "font-style") ) {
+ if(token != G_TOKEN_IDENTIFIER) {
+ g_scanner_error(scanner, "CSS Error: italic/normal expected");
+ return FALSE;
+ }
+
+ if( !strcmp(value.v_identifier, "italic") )
+ g_object_set(current_tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
+ else if( !strcmp(value.v_identifier, "normal") )
+ g_object_set(current_tag, "style", PANGO_STYLE_NORMAL, "style-set", TRUE, NULL);
+ else {
+ g_scanner_error(scanner, "CSS Error: italic/normal expected");
+ return FALSE;
+ }
+ }
+ else if( !strcmp(hint, "font-size") ) {
+ if(token == G_TOKEN_INT)
+ g_object_set(current_tag, "size-points", (float)value.v_int, "size-set", TRUE, NULL);
+ else if(token == G_TOKEN_FLOAT)
+ g_object_set(current_tag, "size-points", value.v_float, "size-set", TRUE, NULL);
+ else {
+ g_scanner_error(scanner, "CSS Error: integer or float expected");
+ return FALSE;
+ }
+ }
+ else if( !strcmp(hint, "color") ) {
+ if(token != G_TOKEN_IDENTIFIER) {
+ g_scanner_error(scanner, "CSS Error: hex color expected");
+ return FALSE;
+ }
+
+ g_object_set(current_tag, "foreground", value.v_identifier, "foreground-set", TRUE, NULL);
+ }
+ else if( !strcmp(hint, "background-color") ) {
+ if(token != G_TOKEN_IDENTIFIER) {
+ g_scanner_error(scanner, "CSS Error: hex color expected");
+ return FALSE;
+ }
+ g_object_set(current_tag, "background", value.v_identifier, "background-set", TRUE, NULL);
+ }
+ else if( !strcmp(hint, "text-align") ) {
+ if(token != G_TOKEN_IDENTIFIER) {
+ g_scanner_error(scanner, "CSS Error: left/right/center expected");
+ return FALSE;
+ }
+
+ if( !strcmp(value.v_identifier, "left") )
+ g_object_set(current_tag, "justification", GTK_JUSTIFY_LEFT, "justification-set", TRUE, NULL);
+ else if( !strcmp(value.v_identifier, "right") )
+ g_object_set(current_tag, "justification", GTK_JUSTIFY_RIGHT, "justification-set", TRUE, NULL);
+ else if( !strcmp(value.v_identifier, "center") )
+ g_object_set(current_tag, "justification", GTK_JUSTIFY_CENTER, "justification-set", TRUE, NULL);
+ else {
+ g_scanner_error(scanner, "CSS Error: left/right/center expected");
+ return FALSE;
+ }
+ }
+ else if( !strcmp(hint, "margin-left") ) {
+ if(token != G_TOKEN_INT) {
+ g_scanner_error(scanner, "CSS Error: integer expected");
+ return FALSE;
+ }
+ g_object_set(current_tag, "left-margin", value.v_int, "left-margin-set", TRUE, NULL);
+ }
+ else if( !strcmp(hint, "margin-right") ) {
+ if(token != G_TOKEN_INT) {
+ g_scanner_error(scanner, "CSS Error: integer expected");
+ return FALSE;
+ }
+ g_object_set(current_tag, "right-margin", value.v_int, "right-margin-set", TRUE, NULL);
+ }
+ else if( !strcmp(hint, "margin-top") ) {
+ if(token != G_TOKEN_INT) {
+ g_scanner_error(scanner, "CSS Error: integer expected");
+ return FALSE;
+ }
+ g_object_set(current_tag, "pixels-above-lines", value.v_int, "pixels-above-lines-set", TRUE, NULL);
+ }
+ else if( !strcmp(hint, "margin-bottom") ) {
+ if(token != G_TOKEN_INT) {
+ g_scanner_error(scanner, "CSS Error: integer expected");
+ return FALSE;
+ }
+ g_object_set(current_tag, "pixels-below-lines", value.v_int, "pixels-below-lines-set", TRUE, NULL);
+ }
+
+ else {
+ g_scanner_error(scanner, "CSS Error: invalid style hint %s", hint);
+ return FALSE;
+ }
+
+ if( !style_accept(scanner, ';') )
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Internal function: parses a glk color to a #hex-value */
+static void
+glkcolor_to_hex(glui32 val, gchar *buffer)
+{
+ g_return_if_fail(buffer != NULL);
+