/* css.c: Cascade-management routines for libRUIN * Copyright (C) 2007 Julian Graham * * libRUIN is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * libRUIN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with libRUIN; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include "css.h" #include "layout.h" #include "libruin.h" #include "scheme.h" #include "util.h" /* The orders here have to match the orders of the enumerations in the header file! */ const char *ruin_css_fg_color_hex[16] = { "#000000", "#800000", "#008000", "#808000", "#000080", "#800080", "#008080", "#c0c0c0", "#808080", "#ff0000", "#00ff00", "#ffff00", "#0000ff", "#ff00ff", "#00ffff", "#ffffff" }; const char *ruin_css_bg_color_hex[8] = { "#000000", "#800000", "#008000", "#808000", "#000080", "#800080", "#008080", "#c0c0c0" }; long full_value_select_time = 0; int full_value_select_num = 0; static SCM sch_p = SCM_EOL; static SCM ssas_p = SCM_EOL; static SCM ssuas_p = SCM_EOL; static SCM ssvan_p = SCM_EOL; static SCM ssv_p = SCM_EOL; static SCM sip_p = SCM_EOL; static SCM sgdv_p = SCM_EOL; static SCM scsc_p = SCM_EOL; int ruin_css_get_rgb(char *c) { SCM sc = SCM_EOL; SCM val = scm_from_int32(0); if (sch_p == SCM_EOL) { sch_p = scm_c_eval_string("scss:color->hex"); } if (c[0] == '#') val = scm_string_to_number(scm_makfrom0str(c + 1), scm_from_int32(16)); else { sc = scm_call_1(sch_p, scm_makfrom0str(c)); if (scm_eq_p(sc, SCM_EOL) != SCM_BOOL_T) val = scm_string_to_number(scm_substring(sc, scm_from_int32(1), scm_from_int32(7)), scm_from_int32(16)); } return scm_num2int(val, 0, "libruin"); } double ruin_css_get_color_distance(int c1, int c2) { int d_r = (c2 >> 16) - (c1 >> 16); int d_g = ((c2 & 0xff00) >> 8) - ((c1 & 0xff00) >> 8); int d_b = (c2 & 0xff) - (c1 & 0xff); return sqrt((d_r * d_r) + (d_g * d_g) + (d_b * d_b)); } enum ruin_layout_foreground_color ruin_css_match_foreground_color(char *c) { int rgb = ruin_css_get_rgb(c); int temp_rgb = 0; enum ruin_layout_foreground_color min_distance_color = RUIN_LAYOUT_FG_COLOR_BLACK; double min_distance = -1; int i; for (i = 0; i < 16; i++) { double distance; temp_rgb = ruin_css_get_rgb((char *) ruin_css_fg_color_hex[i]); distance = ruin_css_get_color_distance(rgb, temp_rgb); if ((min_distance == -1) || (distance < min_distance)) { min_distance = distance; min_distance_color = i; } } return min_distance_color; } enum ruin_layout_background_color ruin_css_match_background_color (char *c, ruin_util_list *l) { int rgb = -1; int temp_rgb = 0; enum ruin_layout_background_color min_distance_color = RUIN_LAYOUT_FG_COLOR_BLACK; double min_distance = -1; int i; if (strcmp(c, "transparent") == 0) { while(l != NULL) { ruin_element_t *t = (ruin_element_t *) l->data; c = ruin_css_lookup(t, "background-color", l); if (strcmp(c, "transparent") != 0) { rgb = ruin_css_get_rgb(c); break; } l = l->next; } if (rgb == -1) return min_distance_color; } else rgb = ruin_css_get_rgb(c); for (i = 0; i < 8; i++) { double distance; temp_rgb = ruin_css_get_rgb((char *) ruin_css_bg_color_hex[i]); distance = ruin_css_get_color_distance(rgb, temp_rgb); if ((min_distance == -1) || (distance < min_distance)) { min_distance = distance; min_distance_color = i; } } return min_distance_color; } /* If the element has style based on attribute values, we temporarily append it to the head of the author stylesheet for the purposes of doing the cascade lookup. If the element has a "style" attribute or child or something, this data takes precedence over everything else. */ char *ruin_css_lookup(ruin_element_t *tree, char *prop, ruin_util_list *inh) { SCM cascade_value; SCM backup_sheet = SCM_EOL; SCM scm_prop = scm_makfrom0str(prop); char *cached_value = ruin_util_hash_retrieve(tree->style_cache, prop); ruin_element_t *ancestor = ruin_util_list_length(inh) ? inh->data : NULL; if (ssas_p == SCM_EOL) { ssas_p = scm_c_eval_string("scss:set-author-stylesheet!"); ssuas_p = scm_c_eval_string("scss:set-agent-stylesheet!"); ssvan_p = scm_c_eval_string("scss:select-value-at-node"); ssv_p = scm_c_eval_string("scss:select-value"); sip_p = scm_c_eval_string("scss:inherited?"); sgdv_p = scm_c_eval_string("scss:get-default-value"); } if (cached_value != NULL) return cached_value; if (scm_eq_p(tree->inherent_attribute_style, SCM_EOL) != SCM_BOOL_T) { /* CSS2.1 6.4.4 says that HTML attribute-based style goes into the author sheet, style from other document languages goes into the agent sheet. */ if (tree->dialect == RUIN_LAYOUT_XML_DIALECT_XHTML) { backup_sheet = SCM_CADDR(tree->cascade); scm_call_2(ssas_p, tree->cascade, scm_cons(SCM_CAR(tree->inherent_attribute_style), backup_sheet)); } else { backup_sheet = SCM_CADR(tree->cascade); scm_call_2(ssuas_p, tree->cascade, scm_cons(SCM_CAR(tree->inherent_attribute_style), backup_sheet)); } } if (scm_string_p(tree->element) != SCM_BOOL_T) { long s = ruin_util_current_time_millis(); cascade_value = scm_call_4 (ssvan_p, tree->cascade, tree->element, tree->doc, scm_prop); full_value_select_time += ruin_util_current_time_millis() - s; full_value_select_num++; } else if (strcmp(scm_to_locale_string(tree->element), "ruin-inline-element") == 0) { if (strcmp(prop, "display") == 0) cascade_value = scm_makfrom0str("inline"); else { long s = ruin_util_current_time_millis(); cascade_value = scm_call_4 (ssvan_p, tree->cascade, tree->parent->element, tree->doc, scm_prop); full_value_select_time += ruin_util_current_time_millis() - s; full_value_select_num++; } } else { long s = ruin_util_current_time_millis(); cascade_value = scm_call_3(ssv_p, tree->cascade, tree->element, scm_prop); full_value_select_time += ruin_util_current_time_millis() - s; full_value_select_num++; } if (scm_eq_p(tree->inherent_attribute_style, SCM_EOL) != SCM_BOOL_T) { scm_call_2(tree->dialect == RUIN_LAYOUT_XML_DIALECT_XHTML ? ssas_p : ssuas_p, tree->cascade, backup_sheet); } { ruin_element_t *as_location = NULL; if (scm_eq_p(tree->additional_attribute_style, SCM_EOL) != SCM_BOOL_T) { as_location = tree; } else if (tree->content != NULL && scm_eq_p(tree->parent->additional_attribute_style, SCM_EOL) != SCM_BOOL_T) { /* If the element is an inline element with content, we need to check whether the parent element has additional attribute style. */ as_location = tree->parent; } if (as_location != NULL) { long s = ruin_util_current_time_millis(); SCM eas_res = scm_call_3 (ssvan_p, scm_list_3(SCM_EOL, SCM_EOL, as_location->additional_attribute_style), scm_makfrom0str("ruin-additional-style-lookup"), scm_prop); full_value_select_time += ruin_util_current_time_millis() - s; full_value_select_num++; if (scm_eq_p(eas_res, SCM_EOL) != SCM_BOOL_T) cascade_value = eas_res; } } if ((scm_eq_p(cascade_value, SCM_EOL) != SCM_BOOL_T) && (scm_equal_p(cascade_value, scm_makfrom0str("inherit"))) != SCM_BOOL_T) { char *val = strdup(scm_to_locale_string(cascade_value)); ruin_util_hash_insert(tree->style_cache, prop, val); return val; } else { char *val = NULL; if ((ancestor == NULL) || ((scm_call_1(sip_p, scm_prop) == SCM_BOOL_F) && ((strcmp(ruin_css_lookup(ancestor, "display", inh->next), "inline") != 0) || (scm_equal_p (ancestor->element, scm_makfrom0str("ruin-inline-element")) == SCM_BOOL_T)))) { /* The above expression says, "Don't try to inherit a value if the parent node is NULL or if the value isn't inheritable -- UNLESS the parent element is an inline element with no content (i.e., it's a tag or something)." */ long s = ruin_util_current_time_millis(); cascade_value = scm_call_1(sgdv_p, scm_prop); full_value_select_time += ruin_util_current_time_millis() - s; full_value_select_num++; val = strdup(scm_to_locale_string(cascade_value)); ruin_util_hash_insert(tree->style_cache, prop, val); } else { val = strdup(ruin_css_lookup(ancestor, prop, inh->next)); if (val != NULL) ruin_util_hash_insert(tree->style_cache, prop, val); } return val; } } void ruin_css_clear_style_cache(ruin_element_t *t) { if (t == NULL) return; ruin_util_hash_clear(t->style_cache); if (scsc_p == SCM_EOL) { scsc_p = scm_c_eval_string("scss:clear-style-cache!"); } if (scm_string_p(t->element) == SCM_BOOL_T) scm_call_2(scsc_p, t->cascade, t->element); else scm_call_3(scsc_p, t->cascade, t->element, t->doc); ruin_css_clear_style_cache(t->first_child); ruin_css_clear_style_cache(t->next_sibling); }