diff --git a/System/Libraries/Html/Renderer.HC b/System/Libraries/Html/Renderer.HC index e1e5bfb..c156f26 100644 --- a/System/Libraries/Html/Renderer.HC +++ b/System/Libraries/Html/Renderer.HC @@ -93,7 +93,7 @@ class @html_renderer #define HtmlRenderer @html_renderer -#define HTML_WORK_BUFFER_SIZE 2048 +#define HTML_WORK_BUFFER_SIZE 4096 // Initialize CSS default rules JsonArray* CSS_DEFAULT_RULES = Json.CreateArray(erythros_mem_task); @@ -283,34 +283,361 @@ U32 @css_resolve_color_from_rrggbb(U8* str) } } -Bool @apply_css_rules_to_node(@html_dom_node* node, HtmlRenderer* renderer) +Bool @apply_css_properties_to_node(@html_dom_node* node, JsonObject* properties) { - I64 i, j, k; - JsonObject* rule = NULL; - JsonArray* matches = NULL; - JsonObject* properties = NULL; - JsonKey* key = NULL; - JsonArray* values = NULL; - U8* selector = NULL; - Bool matched = FALSE; Bool should_display = TRUE; - U8 node_classes_buffer[HTML_WORK_BUFFER_SIZE]; - U8 prepend_buffer[64]; - U8 append_buffer[64]; - MemSet(prepend_buffer, 0, 64); - MemSet(append_buffer, 0, 64); + I64 i, j; + JsonArray* values = NULL; + JsonKey* font_key = NULL; + JsonKey* key = properties->keys; U8 node_tmpnum_buf[16]; - U8** node_classes; - I64 node_classes_count = 0; - I64 color = TRANSPARENT; - U8 node_ptr_string[32]; - U8* tmpmd5; - U8* match_font_family; - JsonKey* font_key; + U8* match_font_family = NULL; - if (!node) + for (i = 0; i < properties->length; i++) { + values = properties->@(key->name); + + if (!StrICmp(key->name, "display")) { + if (!StrICmp(values->@(0), "none")) { + should_display = FALSE; + node->display = CSS_DISPLAY_NONE; + } else { + should_display = TRUE; + node->display = CSS_DISPLAY_INLINE; // default to inline + if (!StrICmp(values->@(0), "block")) { + node->display = CSS_DISPLAY_BLOCK; + } + if (!StrICmp(values->@(0), "inline")) { + node->display = CSS_DISPLAY_INLINE; + } + if (!StrICmp(values->@(0), "inline-block")) { + node->display = CSS_DISPLAY_INLINE_BLOCK; + } + } + } + + if (!StrICmp(key->name, "background") || !StrICmp(key->name, "background-color")) { + if (@css_named_colors->@(values->@(0))) { + node->backgroundColor = @css_resolve_color_from_rrggbb(@css_named_colors->@(values->@(0))); + } else if (values->@(0)(U8*)[0] == '#') { + node->backgroundColor = @css_resolve_color_from_rrggbb(values->@(0)); + } else { + // unsupported + } + } + + if (!StrICmp(key->name, "color")) { + if (@css_named_colors->@(values->@(0))) { + node->color = @css_resolve_color_from_rrggbb(@css_named_colors->@(values->@(0))); + } else if (values->@(0)(U8*)[0] == '#') { + node->color = @css_resolve_color_from_rrggbb(values->@(0)); + } else { + // unsupported + } + } + + if (!StrICmp(key->name, "width") && !StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "px")) { + StrCpy(node_tmpnum_buf, values->@(0)); + node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; + node->width = Str2I64(node_tmpnum_buf); + } + + if (!StrICmp(key->name, "height") && !StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "px")) { + StrCpy(node_tmpnum_buf, values->@(0)); + node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; + node->height = Str2I64(node_tmpnum_buf); + } + + if (!StrICmp(key->name, "text-align") && !StrICmp(values->@(0), "left")) + node->textAlign = CSS_TEXT_ALIGN_LEFT; + if (!StrICmp(key->name, "text-align") && !StrICmp(values->@(0), "center")) + node->textAlign = CSS_TEXT_ALIGN_CENTER; + if (!StrICmp(key->name, "text-align") && !StrICmp(values->@(0), "right")) + node->textAlign = CSS_TEXT_ALIGN_RIGHT; + + if (!StrICmp(key->name, "text-decoration") || !StrICmp(key->name, "text-decoration-line")) { + if (!StrICmp(values->@(0), "none")) { + node->linethroughColor = 0; + node->underlineColor = 0; + } + if (!StrICmp(values->@(0), "line-through")) { + node->linethroughColor = node->color; + } + if (!StrICmp(values->@(0), "underline")) { + node->underlineColor = node->color; + } + } + + if (!StrICmp(key->name, "line-height") && !StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "px")) { + StrCpy(node_tmpnum_buf, values->@(0)); + node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; + node->fontSize = ToI64((Str2I64(node_tmpnum_buf) / 3) * 2); + } + + if (!StrICmp(key->name, "font-size")) { + StrCpy(node_tmpnum_buf, values->@(0)); + if (!StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "em")) { + node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; + node->fontSize = ToI64(Str2F64(node_tmpnum_buf) * RENDERER_DEFAULT_MAX_LINE_HEIGHT); + } + if (!StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "pt")) { + node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; + node->fontSize = ToI64(Str2F64(node_tmpnum_buf) * 1.33333333); + } + if (!StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "px")) { + node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; + node->fontSize = Str2I64(node_tmpnum_buf); + } + } + + if (!StrICmp(key->name, "font-weight")) { + if (values->@(0)(U8*)[0] >= '0' && values->@(0)(U8*)[0] <= '9') { + node->fontWeight = Str2I64(values->@(0)); + } + if (!StrICmp(values->@(0), "bold")) { + node->fontWeight = 700; + } + if (!StrICmp(values->@(0), "normal")) { + node->fontWeight = 400; + } + } + + if (!StrICmp(key->name, "font-family")) { + for (j = 0; j < values->length; j++) { + + match_font_family = values->@(j); + String.Trim(match_font_family); + String.Trim(match_font_family, '"'); + + font_key = Fonts->keys; + while (font_key) { + if (!StrICmp(font_key->name, match_font_family)) { + node->fontFamily = font_key->name; + goto css_continue_to_next_property; + } + font_key = font_key->next; + } + } + } + + if (!StrICmp(key->name, "font-style")) { + if (!StrICmp(values->@(0), "italic")) { + node->italic = TRUE; + } + } + css_continue_to_next_property: + key = key->next; + } + return should_display; +} + +Bool @css_selector_contains_combinator(U8* selector) +{ + U8* combinators = ">| +~"; + while (*selector) { + if (StrOcc(combinators, *selector)) + return TRUE; + ++selector; + } + return FALSE; +} + +Bool @css_selector_is_compound_selector(U8* selector) +{ + U8* tokens = ".:[#"; + ++selector; + while (*selector) { + if (StrOcc(tokens, *selector)) + return TRUE; + ++selector; + } + return FALSE; +} + +Bool @node_matches_simple_selector(@html_dom_node* node, U8* selector) +{ + //"node: 0x%08x, selector: %s, ", node, selector; + //"attributes: %s\n", Json.Stringify(node->attributes, erythros_mem_task); + Bool match = FALSE; + U8** node_classes = NULL; + I64 node_classes_count = 0; + U8 buffer[HTML_WORK_BUFFER_SIZE]; + U8* name = NULL; + U8* value = NULL; + + I64 i = 0; + U8 ptr_to_c_string[32]; + U8* ptr_to_md5_string = NULL; + switch (*selector) { + case '\xfe': + StrPrint(ptr_to_c_string, "0x%08x", node); + ptr_to_md5_string = md5_string(ptr_to_c_string, StrLen(ptr_to_c_string)); + if (!StrCmp(selector + 1, ptr_to_md5_string)) { + match = TRUE; + } + Free(ptr_to_md5_string); + return match; + case '#': + if (!node->attributes->@("id")) + return FALSE; + return !StrCmp(selector + 1, node->attributes->@("id")); + case '.': + if (!node->attributes->@("class")) + return FALSE; + if (!StrOcc(node->attributes->@("class"), ' ')) { + if (!StrCmp(selector + 1, node->attributes->@("class"))) { + match = TRUE; + } + } else { + MemSet(buffer, 0, HTML_WORK_BUFFER_SIZE); + StrCpy(buffer, node->attributes->@("class")); + node_classes = String.Split(buffer, ' ', &node_classes_count); + for (i = 0; i < node_classes_count; i++) { + if (!StrCmp(selector + 1, node_classes[i])) { + match = TRUE; + i = node_classes_count; + } + } + Free(node_classes); + } + return match; + case '[': + if (selector[StrLen(selector) - 1] == ']') { + StrCpy(buffer, selector + 1); + value = buffer + StrLen(buffer) - 1; + while (StrOcc("'\"]", *value)) { + *value = NULL; + --value; + } + while (!StrOcc("'\"=", *value)) { + --value; + } + name = value; + ++value; + while (StrOcc("'\"=", *value)) { + *name = NULL; + --name; + } + name = buffer; + if (StrLen(name) && node->attributes->@(name) && !StrCmp(node->attributes->@(name), value)) { + match = TRUE; + } + } + return match; + default: + break; + } + return !StrICmp(node->tagName, selector); +} + +Bool @node_matches_compound_selector(@html_dom_node* node, U8* compound_selector) +{ + U8 buffer[HTML_WORK_BUFFER_SIZE]; + U8* tokens = ".:[#"; + I64 i = 0; + I64 j = 1; + I64 length = StrLen(compound_selector); + while (j && j < length) { + if (StrOcc(tokens, compound_selector[j])) { + MemSet(buffer, 0, length); + MemCpy(buffer, compound_selector + i, j - i); + i = j; + //"nmcs: try: 0x%08x, %s\n", node, buffer; + if (!@node_matches_simple_selector(node, buffer)) + return FALSE; + } + ++j; + } + //"nmcs: end: 0x%08x, %s\n", node, compound_selector + i; + return @node_matches_simple_selector(node, compound_selector + i); +} + +@html_dom_node* @ancestor_who_matches_compound_selector(@html_dom_node* node, U8* selector) +{ + while (node) { + //"amcs: node: 0x%08x, selector: %s\n", node, selector; + if (@node_matches_compound_selector(node, selector)) + return node; + node = node->parentNode; + } + return NULL; +} + +Bool @node_matches_selector_with_combinator(@html_dom_node* node, U8* selector_with_combinator) +{ + //"nmswc: node: 0x%08x, swc: %s\n", node, selector_with_combinator; + // FIXME: We only handle the descendant combinator for now + U8* ch = selector_with_combinator; + while (*ch) { + if (StrOcc(">|+~", *ch)) + return FALSE; + ++ch; + } + + @html_dom_node* ancestor = NULL; + U8 buffer[HTML_WORK_BUFFER_SIZE]; + StrCpy(buffer, selector_with_combinator); + String.Trim(buffer); + U8* selector_for_ancestor = NULL; + + U8* selector_for_node = buffer + StrLen(buffer) - 1; + while (*selector_for_node != ' ') { + --selector_for_node; + } + + ancestor = node->parentNode; + selector_for_ancestor = selector_for_node; + ++selector_for_node; + + if (!@node_matches_compound_selector(node, selector_for_node)) return FALSE; - if (node->parentNode) { + + // We matched the selector for node, now let's match the selectors for ancestors + + while (selector_for_ancestor > buffer) { + // null terminate the selector for ancestor + while (*selector_for_ancestor == ' ') { + *selector_for_ancestor = NULL; + --selector_for_ancestor; + } + // move selector for ancestor pointer to the left until we either: + // 1) hit the beginning of the buffer, or + // 2) hit whitespace + while (selector_for_ancestor > buffer && (*selector_for_ancestor != ' ')) { + --selector_for_ancestor; + } + if (*selector_for_ancestor == ' ') + ++selector_for_ancestor; + ancestor = @ancestor_who_matches_compound_selector(ancestor, selector_for_ancestor); + if (!ancestor) + return FALSE; + --selector_for_ancestor; + } + // if we got here, we must be a match + return TRUE; +} + +Bool @node_matches_css_selector(@html_dom_node* node, U8* selector) +{ + // We need to handle combinators ("div ol li"), compound selectors "p.class#id", simple selectors + + // FIXME: Handle combinators + if (@css_selector_contains_combinator(selector)) { + return @node_matches_selector_with_combinator(node, selector); + return FALSE; + } + + // FIXME: Handle compound selectors + if (@css_selector_is_compound_selector(selector)) { + return @node_matches_compound_selector(node, selector); + return FALSE; + } + + return @node_matches_simple_selector(node, selector); +} + +U0 @inherit_css_values_from_parent_node(@html_dom_node* node) +{ + if (node && node->parentNode) { node->backgroundColor = node->parentNode->backgroundColor; node->color = node->parentNode->color; node->linethroughColor = node->parentNode->linethroughColor; @@ -321,206 +648,40 @@ Bool @apply_css_rules_to_node(@html_dom_node* node, HtmlRenderer* renderer) node->textAlign = node->parentNode->textAlign; node->italic = node->parentNode->italic; } +} + +Bool @apply_css_rules_to_node(@html_dom_node* node, HtmlRenderer* renderer) +{ + + if (!node) + return FALSE; + + I64 i, j; + JsonObject* rule = NULL; + JsonArray* matches = NULL; + JsonObject* properties = NULL; + U8* selector = NULL; + Bool matched = FALSE; + Bool should_display = TRUE; + + @inherit_css_values_from_parent_node(node); for (i = 0; i < renderer->css_rules->length; i++) { rule = renderer->css_rules->@(i); matched = FALSE; if (rule->@("matches")) { - matches = rule->@("matches"); properties = rule->@("properties"); - - // check if node md5 hash matches - if (*(matches->@(0)(U8*)) == 0xFE) { - StrPrint(node_ptr_string, "0x%08x", node); - tmpmd5 = md5_string(node_ptr_string, StrLen(node_ptr_string)); - if (!StrCmp(matches->@(0) + 1, tmpmd5)) { - matched = TRUE; - Free(tmpmd5); - goto @css_rule_check_if_matched; - } - Free(tmpmd5); - } - - // try to match id - if (*(matches->@(0)(U8*)) == '#' && node->attributes->@("id")) { - if (!StrCmp(matches->@(0) + 1, node->attributes->@("id"))) { - matched = TRUE; - goto @css_rule_check_if_matched; - } - } - - // try to match selectors for (j = 0; j < matches->length; j++) { selector = matches->@(j); - - if (node->attributes->@("class") && StrFirstOcc(selector, ".")) { - // node has class attribute and current selector has .class - - if (!StrFirstOcc(node->attributes->@("class"), " ")) { - if (!StrCmp(node->attributes->@("class"), StrFirstOcc(selector, ".") + 1)) { - matched = TRUE; - goto @css_rule_check_if_matched; - } - } else { - MemSet(node_classes_buffer, 0, HTML_WORK_BUFFER_SIZE); - StrCpy(node_classes_buffer, node->attributes->@("class")); - node_classes = String.Split(node_classes_buffer, ' ', &node_classes_count); - - for (k = 0; k < node_classes_count; k++) { - if (!StrCmp(node_classes[k], StrFirstOcc(selector, ".") + 1)) { - matched = TRUE; - Free(node_classes); - goto @css_rule_check_if_matched; - } - } - } + if (@node_matches_css_selector(node, selector)) { + matched = TRUE; + goto @css_rule_check_if_matched; } } - - // try to match tagName - if (!StrICmp(matches->@(0), node->tagName)) { - matched = TRUE; - goto @css_rule_check_if_matched; - } - @css_rule_check_if_matched : if (matched) { - key = properties->keys; - for (j = 0; j < properties->length; j++) { - values = properties->@(key->name); - - if (!StrICmp(key->name, "display")) { - if (!StrICmp(values->@(0), "none")) { - should_display = FALSE; - node->display = CSS_DISPLAY_NONE; - } else { - should_display = TRUE; - node->display = CSS_DISPLAY_INLINE; // default to inline - if (!StrICmp(values->@(0), "block")) { - node->display = CSS_DISPLAY_BLOCK; - } - if (!StrICmp(values->@(0), "inline")) { - node->display = CSS_DISPLAY_INLINE; - } - if (!StrICmp(values->@(0), "inline-block")) { - node->display = CSS_DISPLAY_INLINE_BLOCK; - } - } - } - - if (!StrICmp(key->name, "background") || !StrICmp(key->name, "background-color")) { - if (@css_named_colors->@(values->@(0))) { - node->backgroundColor = @css_resolve_color_from_rrggbb(@css_named_colors->@(values->@(0))); - } else if (values->@(0)(U8*)[0] == '#') { - node->backgroundColor = @css_resolve_color_from_rrggbb(values->@(0)); - } else { - // unsupported - } - } - - if (!StrICmp(key->name, "color")) { - if (@css_named_colors->@(values->@(0))) { - node->color = @css_resolve_color_from_rrggbb(@css_named_colors->@(values->@(0))); - } else if (values->@(0)(U8*)[0] == '#') { - node->color = @css_resolve_color_from_rrggbb(values->@(0)); - } else { - // unsupported - } - } - - if (!StrICmp(key->name, "width") && !StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "px")) { - StrCpy(node_tmpnum_buf, values->@(0)); - node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; - node->width = Str2I64(node_tmpnum_buf); - } - - if (!StrICmp(key->name, "height") && !StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "px")) { - StrCpy(node_tmpnum_buf, values->@(0)); - node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; - node->height = Str2I64(node_tmpnum_buf); - } - - if (!StrICmp(key->name, "text-align") && !StrICmp(values->@(0), "left")) - node->textAlign = CSS_TEXT_ALIGN_LEFT; - if (!StrICmp(key->name, "text-align") && !StrICmp(values->@(0), "center")) - node->textAlign = CSS_TEXT_ALIGN_CENTER; - if (!StrICmp(key->name, "text-align") && !StrICmp(values->@(0), "right")) - node->textAlign = CSS_TEXT_ALIGN_RIGHT; - - if (!StrICmp(key->name, "text-decoration") || !StrICmp(key->name, "text-decoration-line")) { - if (!StrICmp(values->@(0), "none")) { - node->linethroughColor = 0; - node->underlineColor = 0; - } - if (!StrICmp(values->@(0), "line-through")) { - node->linethroughColor = node->color; - } - if (!StrICmp(values->@(0), "underline")) { - node->underlineColor = node->color; - } - } - - if (!StrICmp(key->name, "line-height") && !StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "px")) { - StrCpy(node_tmpnum_buf, values->@(0)); - node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; - node->fontSize = ToI64((Str2I64(node_tmpnum_buf) / 3) * 2); - } - - if (!StrICmp(key->name, "font-size")) { - StrCpy(node_tmpnum_buf, values->@(0)); - if (!StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "em")) { - node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; - node->fontSize = ToI64(Str2F64(node_tmpnum_buf) * RENDERER_DEFAULT_MAX_LINE_HEIGHT); - } - if (!StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "pt")) { - node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; - node->fontSize = ToI64(Str2F64(node_tmpnum_buf) * 1.33333333); - } - if (!StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "px")) { - node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; - node->fontSize = Str2I64(node_tmpnum_buf); - } - } - - if (!StrICmp(key->name, "font-weight")) { - if (values->@(0)(U8*)[0] >= '0' && values->@(0)(U8*)[0] <= '9') { - node->fontWeight = Str2I64(values->@(0)); - } - if (!StrICmp(values->@(0), "bold")) { - node->fontWeight = 700; - } - if (!StrICmp(values->@(0), "normal")) { - node->fontWeight = 400; - } - } - - if (!StrICmp(key->name, "font-family")) { - for (k = 0; k < values->length; k++) { - - match_font_family = values->@(k); - String.Trim(match_font_family); - String.Trim(match_font_family, '"'); - - font_key = Fonts->keys; - while (font_key) { - if (!StrICmp(font_key->name, match_font_family)) { - node->fontFamily = font_key->name; - goto css_continue_to_next_property; - } - font_key = font_key->next; - } - } - } - - if (!StrICmp(key->name, "font-style")) { - if (!StrICmp(values->@(0), "italic")) { - node->italic = TRUE; - } - } - css_continue_to_next_property: - key = key->next; - } + should_display = @apply_css_properties_to_node(node, properties); } } }