System/Libraries/Html/Renderer: A whole bunch of CSS changes

We now handle simple and compound selectors, but not yet pseudo-classes
or (most) combinators.
This commit is contained in:
Alec Murphy 2025-04-24 15:17:15 -04:00
parent fc2b4ba4e5
commit 3bf6238aac

View file

@ -93,7 +93,7 @@ class @html_renderer
#define HtmlRenderer @html_renderer #define HtmlRenderer @html_renderer
#define HTML_WORK_BUFFER_SIZE 2048 #define HTML_WORK_BUFFER_SIZE 4096
// Initialize CSS default rules // Initialize CSS default rules
JsonArray* CSS_DEFAULT_RULES = Json.CreateArray(erythros_mem_task); JsonArray* CSS_DEFAULT_RULES = Json.CreateArray(erythros_mem_task);
@ -283,111 +283,17 @@ 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; Bool should_display = TRUE;
U8 node_classes_buffer[HTML_WORK_BUFFER_SIZE]; I64 i, j;
U8 prepend_buffer[64]; JsonArray* values = NULL;
U8 append_buffer[64]; JsonKey* font_key = NULL;
MemSet(prepend_buffer, 0, 64); JsonKey* key = properties->keys;
MemSet(append_buffer, 0, 64);
U8 node_tmpnum_buf[16]; U8 node_tmpnum_buf[16];
U8** node_classes; U8* match_font_family = NULL;
I64 node_classes_count = 0;
I64 color = TRANSPARENT;
U8 node_ptr_string[32];
U8* tmpmd5;
U8* match_font_family;
JsonKey* font_key;
if (!node) for (i = 0; i < properties->length; i++) {
return FALSE;
if (node->parentNode) {
node->backgroundColor = node->parentNode->backgroundColor;
node->color = node->parentNode->color;
node->linethroughColor = node->parentNode->linethroughColor;
node->underlineColor = node->parentNode->underlineColor;
node->fontFamily = node->parentNode->fontFamily;
node->fontSize = node->parentNode->fontSize;
node->fontWeight = node->parentNode->fontWeight;
node->textAlign = node->parentNode->textAlign;
node->italic = node->parentNode->italic;
}
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;
}
}
}
}
}
// 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); values = properties->@(key->name);
if (!StrICmp(key->name, "display")) { if (!StrICmp(key->name, "display")) {
@ -496,9 +402,9 @@ Bool @apply_css_rules_to_node(@html_dom_node* node, HtmlRenderer* renderer)
} }
if (!StrICmp(key->name, "font-family")) { if (!StrICmp(key->name, "font-family")) {
for (k = 0; k < values->length; k++) { for (j = 0; j < values->length; j++) {
match_font_family = values->@(k); match_font_family = values->@(j);
String.Trim(match_font_family); String.Trim(match_font_family);
String.Trim(match_font_family, '"'); String.Trim(match_font_family, '"');
@ -521,6 +427,261 @@ Bool @apply_css_rules_to_node(@html_dom_node* node, HtmlRenderer* renderer)
css_continue_to_next_property: css_continue_to_next_property:
key = key->next; 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;
// 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;
node->underlineColor = node->parentNode->underlineColor;
node->fontFamily = node->parentNode->fontFamily;
node->fontSize = node->parentNode->fontSize;
node->fontWeight = node->parentNode->fontWeight;
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");
for (j = 0; j < matches->length; j++) {
selector = matches->@(j);
if (@node_matches_css_selector(node, selector)) {
matched = TRUE;
goto @css_rule_check_if_matched;
}
}
@css_rule_check_if_matched : if (matched)
{
should_display = @apply_css_properties_to_node(node, properties);
} }
} }
} }