From 7b9663887e4569c6d5d3d2c25c41d7fd9365ccc7 Mon Sep 17 00:00:00 2001 From: Leonmmcoset Date: Sat, 25 Apr 2026 15:10:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E5=A5=BD=E7=9A=84=E6=B5=8F=E8=A7=88?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cleonos/c/apps/browser_main.c | 829 ++++++++++++++++++++++++++++++---- clks | 2 +- 2 files changed, 746 insertions(+), 85 deletions(-) diff --git a/cleonos/c/apps/browser_main.c b/cleonos/c/apps/browser_main.c index c4267de..71a56e7 100644 --- a/cleonos/c/apps/browser_main.c +++ b/cleonos/c/apps/browser_main.c @@ -24,6 +24,8 @@ #define USH_BROWSER_SEG_MAX 32U #define USH_BROWSER_SEG_LEN_MAX 63U #define USH_BROWSER_CSS_TEXT_MAX 4096U +#define USH_BROWSER_CSS_RULE_MAX 160U +#define USH_BROWSER_CSS_IDENT_MAX 48U #define USH_BROWSER_ANSI_RESET "\x1B[0m" #define USH_BROWSER_ANSI_BLUE "\x1B[34m" #define USH_BROWSER_ANSI_UNDERLINE "\x1B[4m" @@ -31,6 +33,7 @@ typedef unsigned char u8; typedef unsigned short u16; +typedef unsigned int u32; typedef struct ush_browser_url { char host[USH_BROWSER_HOST_MAX]; @@ -43,6 +46,36 @@ typedef struct ush_browser_link { char href[USH_BROWSER_LINK_HREF_MAX]; } ush_browser_link; +typedef struct ush_browser_style { + int fg_set; + u32 fg_rgb; + int bg_set; + u32 bg_rgb; + int bold; + int underline; + int display_none; +} ush_browser_style; + +typedef struct ush_browser_style_delta { + int set_fg; + u32 fg_rgb; + int set_bg; + u32 bg_rgb; + int set_bold; + int bold; + int set_underline; + int underline; + int set_display_none; + int display_none; +} ush_browser_style_delta; + +typedef struct ush_browser_css_rule { + char tag[USH_BROWSER_CSS_IDENT_MAX]; + char class_name[USH_BROWSER_CSS_IDENT_MAX]; + char id_name[USH_BROWSER_CSS_IDENT_MAX]; + ush_browser_style_delta delta; +} ush_browser_css_rule; + static char ush_browser_html_buf[USH_BROWSER_HTML_MAX + 1U]; static char ush_browser_http_raw_buf[USH_BROWSER_HTML_MAX + 1U]; static char ush_browser_text_buf[USH_BROWSER_TEXT_MAX + 1U]; @@ -52,8 +85,8 @@ static ush_browser_link ush_browser_links[USH_BROWSER_LINK_MAX]; static u64 ush_browser_text_len = 0ULL; static int ush_browser_last_space = 1; static u64 ush_browser_link_count = 0ULL; -static int ush_browser_css_link_blue = 1; -static int ush_browser_css_link_underline = 1; +static ush_browser_css_rule ush_browser_css_rules[USH_BROWSER_CSS_RULE_MAX]; +static u64 ush_browser_css_rule_count = 0ULL; static int ush_browser_is_http_url(const char *text) { if (text == (const char *)0) { @@ -1154,18 +1187,147 @@ static void ush_browser_text_append_raw(const char *text) { } } -static void ush_browser_text_begin_link_style(int blue, int underline) { - if (blue != 0 && underline != 0) { - ush_browser_text_append_raw(USH_BROWSER_ANSI_BLUE_UNDERLINE); - } else if (blue != 0) { - ush_browser_text_append_raw(USH_BROWSER_ANSI_BLUE); - } else if (underline != 0) { - ush_browser_text_append_raw(USH_BROWSER_ANSI_UNDERLINE); +static void ush_browser_style_reset(ush_browser_style *out_style) { + if (out_style == (ush_browser_style *)0) { + return; + } + + out_style->fg_set = 0; + out_style->fg_rgb = 0U; + out_style->bg_set = 0; + out_style->bg_rgb = 0U; + out_style->bold = 0; + out_style->underline = 0; + out_style->display_none = 0; +} + +static void ush_browser_style_delta_reset(ush_browser_style_delta *out_delta) { + if (out_delta == (ush_browser_style_delta *)0) { + return; + } + + out_delta->set_fg = 0; + out_delta->fg_rgb = 0U; + out_delta->set_bg = 0; + out_delta->bg_rgb = 0U; + out_delta->set_bold = 0; + out_delta->bold = 0; + out_delta->set_underline = 0; + out_delta->underline = 0; + out_delta->set_display_none = 0; + out_delta->display_none = 0; +} + +static int ush_browser_style_equal(const ush_browser_style *a, const ush_browser_style *b) { + if (a == (const ush_browser_style *)0 || b == (const ush_browser_style *)0) { + return 0; + } + + return (a->fg_set == b->fg_set && a->fg_rgb == b->fg_rgb && a->bg_set == b->bg_set && a->bg_rgb == b->bg_rgb && + a->bold == b->bold && a->underline == b->underline && a->display_none == b->display_none) + ? 1 + : 0; +} + +static void ush_browser_style_apply_delta(ush_browser_style *io_style, const ush_browser_style_delta *delta) { + if (io_style == (ush_browser_style *)0 || delta == (const ush_browser_style_delta *)0) { + return; + } + + if (delta->set_fg != 0) { + io_style->fg_set = 1; + io_style->fg_rgb = delta->fg_rgb; + } + if (delta->set_bg != 0) { + io_style->bg_set = 1; + io_style->bg_rgb = delta->bg_rgb; + } + if (delta->set_bold != 0) { + io_style->bold = (delta->bold != 0) ? 1 : 0; + } + if (delta->set_underline != 0) { + io_style->underline = (delta->underline != 0) ? 1 : 0; + } + if (delta->set_display_none != 0) { + io_style->display_none = (delta->display_none != 0) ? 1 : 0; } } -static void ush_browser_text_end_link_style(void) { - ush_browser_text_append_raw(USH_BROWSER_ANSI_RESET); +static void ush_browser_style_apply_anchor_default(ush_browser_style *io_style) { + if (io_style == (ush_browser_style *)0) { + return; + } + + io_style->fg_set = 1; + io_style->fg_rgb = 0x0000FFU; + io_style->underline = 1; +} + +static void ush_browser_text_emit_style(const ush_browser_style *style) { + char seq[128]; + u64 at = 0ULL; + int wrote; + + if (style == (const ush_browser_style *)0) { + return; + } + + wrote = snprintf(seq, sizeof(seq), "\x1B[0"); + if (wrote <= 0 || (u64)wrote >= (u64)sizeof(seq)) { + ush_browser_text_append_raw(USH_BROWSER_ANSI_RESET); + return; + } + at = (u64)wrote; + + if (style->bold != 0) { + wrote = snprintf(seq + at, sizeof(seq) - at, ";1"); + if (wrote <= 0 || (u64)wrote >= (u64)(sizeof(seq) - at)) { + ush_browser_text_append_raw(USH_BROWSER_ANSI_RESET); + return; + } + at += (u64)wrote; + } + + if (style->underline != 0) { + wrote = snprintf(seq + at, sizeof(seq) - at, ";4"); + if (wrote <= 0 || (u64)wrote >= (u64)(sizeof(seq) - at)) { + ush_browser_text_append_raw(USH_BROWSER_ANSI_RESET); + return; + } + at += (u64)wrote; + } + + if (style->fg_set != 0) { + u32 r = (style->fg_rgb >> 16U) & 0xFFU; + u32 g = (style->fg_rgb >> 8U) & 0xFFU; + u32 b = style->fg_rgb & 0xFFU; + wrote = snprintf(seq + at, sizeof(seq) - at, ";38;2;%u;%u;%u", (unsigned int)r, (unsigned int)g, (unsigned int)b); + if (wrote <= 0 || (u64)wrote >= (u64)(sizeof(seq) - at)) { + ush_browser_text_append_raw(USH_BROWSER_ANSI_RESET); + return; + } + at += (u64)wrote; + } + + if (style->bg_set != 0) { + u32 r = (style->bg_rgb >> 16U) & 0xFFU; + u32 g = (style->bg_rgb >> 8U) & 0xFFU; + u32 b = style->bg_rgb & 0xFFU; + wrote = snprintf(seq + at, sizeof(seq) - at, ";48;2;%u;%u;%u", (unsigned int)r, (unsigned int)g, (unsigned int)b); + if (wrote <= 0 || (u64)wrote >= (u64)(sizeof(seq) - at)) { + ush_browser_text_append_raw(USH_BROWSER_ANSI_RESET); + return; + } + at += (u64)wrote; + } + + wrote = snprintf(seq + at, sizeof(seq) - at, "m"); + if (wrote <= 0 || (u64)wrote >= (u64)(sizeof(seq) - at)) { + ush_browser_text_append_raw(USH_BROWSER_ANSI_RESET); + return; + } + + ush_browser_text_append_raw(seq); } static int ush_browser_css_is_space(char ch) { @@ -1189,18 +1351,65 @@ static int ush_browser_css_name_eq(const char *name, u64 name_len, const char *l return 1; } -static int ush_browser_css_color_is_blue(const char *value, u64 value_len) { - char normalized[64]; - u64 at = 0ULL; - u64 i; +static int ush_browser_css_hex_nibble(char ch, u32 *out_value) { + if (out_value == (u32 *)0) { + return 0; + } - if (value == (const char *)0) { + if (ch >= '0' && ch <= '9') { + *out_value = (u32)(ch - '0'); + return 1; + } + if (ch >= 'a' && ch <= 'f') { + *out_value = (u32)(ch - 'a') + 10U; + return 1; + } + if (ch >= 'A' && ch <= 'F') { + *out_value = (u32)(ch - 'A') + 10U; + return 1; + } + return 0; +} + +static int ush_browser_css_parse_u32_dec(const char *text, u64 len, u64 *io_pos, u32 *out_value) { + u64 pos; + u64 acc = 0ULL; + int has_digit = 0; + + if (text == (const char *)0 || io_pos == (u64 *)0 || out_value == (u32 *)0 || *io_pos >= len) { + return 0; + } + + pos = *io_pos; + while (pos < len && text[pos] >= '0' && text[pos] <= '9') { + acc = acc * 10ULL + (u64)(text[pos] - '0'); + if (acc > 0xFFFFFFFFULL) { + return 0; + } + has_digit = 1; + pos++; + } + + if (has_digit == 0) { + return 0; + } + + *io_pos = pos; + *out_value = (u32)acc; + return 1; +} + +static int ush_browser_css_parse_color(const char *value, u64 value_len, u32 *out_rgb) { + char normalized[96]; + u64 i; + u64 at = 0ULL; + + if (value == (const char *)0 || out_rgb == (u32 *)0) { return 0; } for (i = 0ULL; i < value_len; i++) { char ch = value[i]; - if (ush_browser_css_is_space(ch) != 0) { continue; } @@ -1211,18 +1420,134 @@ static int ush_browser_css_color_is_blue(const char *value, u64 value_len) { } normalized[at] = '\0'; - if (ush_streq(normalized, "blue") != 0 || ush_streq(normalized, "#00f") != 0 || ush_streq(normalized, "#0000ff") != 0 || - ush_streq(normalized, "rgb(0,0,255)") != 0) { + if (at == 0ULL) { + return 0; + } + + if (normalized[0] == '#') { + if (at == 4ULL) { + u32 r0; + u32 g0; + u32 b0; + if (ush_browser_css_hex_nibble(normalized[1], &r0) == 0 || ush_browser_css_hex_nibble(normalized[2], &g0) == 0 || + ush_browser_css_hex_nibble(normalized[3], &b0) == 0) { + return 0; + } + *out_rgb = ((r0 << 20U) | (r0 << 16U) | (g0 << 12U) | (g0 << 8U) | (b0 << 4U) | b0); + return 1; + } + if (at == 7ULL) { + u32 n[6]; + for (i = 0ULL; i < 6ULL; i++) { + if (ush_browser_css_hex_nibble(normalized[i + 1ULL], &n[i]) == 0) { + return 0; + } + } + *out_rgb = ((n[0] << 20U) | (n[1] << 16U) | (n[2] << 12U) | (n[3] << 8U) | (n[4] << 4U) | n[5]); + return 1; + } + } + + if (at >= 6ULL && normalized[0] == 'r' && normalized[1] == 'g' && normalized[2] == 'b' && normalized[3] == '(' && + normalized[at - 1ULL] == ')') { + u64 pos = 4ULL; + u32 r; + u32 g; + u32 b; + + if (ush_browser_css_parse_u32_dec(normalized, at - 1ULL, &pos, &r) == 0 || pos >= at - 1ULL || + normalized[pos] != ',' || r > 255U) { + return 0; + } + pos++; + if (ush_browser_css_parse_u32_dec(normalized, at - 1ULL, &pos, &g) == 0 || pos >= at - 1ULL || + normalized[pos] != ',' || g > 255U) { + return 0; + } + pos++; + if (ush_browser_css_parse_u32_dec(normalized, at - 1ULL, &pos, &b) == 0 || b > 255U) { + return 0; + } + *out_rgb = (r << 16U) | (g << 8U) | b; + return 1; + } + + if (ush_streq(normalized, "black") != 0) { + *out_rgb = 0x000000U; + return 1; + } + if (ush_streq(normalized, "white") != 0) { + *out_rgb = 0xFFFFFFU; + return 1; + } + if (ush_streq(normalized, "red") != 0) { + *out_rgb = 0xFF0000U; + return 1; + } + if (ush_streq(normalized, "green") != 0) { + *out_rgb = 0x008000U; + return 1; + } + if (ush_streq(normalized, "blue") != 0) { + *out_rgb = 0x0000FFU; + return 1; + } + if (ush_streq(normalized, "yellow") != 0) { + *out_rgb = 0xFFFF00U; + return 1; + } + if (ush_streq(normalized, "magenta") != 0 || ush_streq(normalized, "fuchsia") != 0) { + *out_rgb = 0xFF00FFU; + return 1; + } + if (ush_streq(normalized, "cyan") != 0 || ush_streq(normalized, "aqua") != 0) { + *out_rgb = 0x00FFFFU; + return 1; + } + if (ush_streq(normalized, "gray") != 0 || ush_streq(normalized, "grey") != 0) { + *out_rgb = 0x808080U; + return 1; + } + if (ush_streq(normalized, "silver") != 0) { + *out_rgb = 0xC0C0C0U; + return 1; + } + if (ush_streq(normalized, "orange") != 0) { + *out_rgb = 0xFFA500U; + return 1; + } + if (ush_streq(normalized, "purple") != 0) { + *out_rgb = 0x800080U; + return 1; + } + if (ush_streq(normalized, "navy") != 0) { + *out_rgb = 0x000080U; + return 1; + } + if (ush_streq(normalized, "teal") != 0) { + *out_rgb = 0x008080U; + return 1; + } + if (ush_streq(normalized, "lime") != 0) { + *out_rgb = 0x00FF00U; + return 1; + } + if (ush_streq(normalized, "maroon") != 0) { + *out_rgb = 0x800000U; + return 1; + } + if (ush_streq(normalized, "olive") != 0) { + *out_rgb = 0x808000U; return 1; } return 0; } -static void ush_browser_css_apply_declarations(const char *decl, u64 decl_len, int *io_link_blue, int *io_link_underline) { +static void ush_browser_css_apply_declarations(const char *decl, u64 decl_len, ush_browser_style_delta *io_delta) { u64 pos = 0ULL; - if (decl == (const char *)0 || io_link_blue == (int *)0 || io_link_underline == (int *)0) { + if (decl == (const char *)0 || io_delta == (ush_browser_style_delta *)0) { return; } @@ -1231,6 +1556,7 @@ static void ush_browser_css_apply_declarations(const char *decl, u64 decl_len, i u64 name_end; u64 value_start; u64 value_end; + u32 rgb; while (pos < decl_len && (ush_browser_css_is_space(decl[pos]) != 0 || decl[pos] == ';')) { pos++; @@ -1272,20 +1598,55 @@ static void ush_browser_css_apply_declarations(const char *decl, u64 decl_len, i } if (name_end > name_start && value_end >= value_start) { + const char *value = decl + value_start; + u64 value_len = value_end - value_start; + if (ush_browser_css_name_eq(decl + name_start, name_end - name_start, "text-decoration") != 0) { - if (ush_browser_line_has_token_icase(decl + value_start, value_end - value_start, "none") != 0) { - *io_link_underline = 0; + if (ush_browser_line_has_token_icase(value, value_len, "none") != 0) { + io_delta->set_underline = 1; + io_delta->underline = 0; } - if (ush_browser_line_has_token_icase(decl + value_start, value_end - value_start, "underline") != 0) { - *io_link_underline = 1; + if (ush_browser_line_has_token_icase(value, value_len, "underline") != 0) { + io_delta->set_underline = 1; + io_delta->underline = 1; } } else if (ush_browser_css_name_eq(decl + name_start, name_end - name_start, "color") != 0) { - if (ush_browser_line_has_token_icase(decl + value_start, value_end - value_start, "inherit") != 0 || - ush_browser_line_has_token_icase(decl + value_start, value_end - value_start, "initial") != 0 || - ush_browser_line_has_token_icase(decl + value_start, value_end - value_start, "unset") != 0) { - /* keep previous style */ - } else { - *io_link_blue = (ush_browser_css_color_is_blue(decl + value_start, value_end - value_start) != 0) ? 1 : 0; + if (ush_browser_css_parse_color(value, value_len, &rgb) != 0) { + io_delta->set_fg = 1; + io_delta->fg_rgb = rgb; + } + } else if (ush_browser_css_name_eq(decl + name_start, name_end - name_start, "background-color") != 0) { + if (ush_browser_css_parse_color(value, value_len, &rgb) != 0) { + io_delta->set_bg = 1; + io_delta->bg_rgb = rgb; + } + } else if (ush_browser_css_name_eq(decl + name_start, name_end - name_start, "font-weight") != 0) { + if (ush_browser_line_has_token_icase(value, value_len, "bold") != 0 || + ush_browser_line_has_token_icase(value, value_len, "600") != 0 || + ush_browser_line_has_token_icase(value, value_len, "700") != 0 || + ush_browser_line_has_token_icase(value, value_len, "800") != 0 || + ush_browser_line_has_token_icase(value, value_len, "900") != 0) { + io_delta->set_bold = 1; + io_delta->bold = 1; + } else if (ush_browser_line_has_token_icase(value, value_len, "normal") != 0 || + ush_browser_line_has_token_icase(value, value_len, "100") != 0 || + ush_browser_line_has_token_icase(value, value_len, "200") != 0 || + ush_browser_line_has_token_icase(value, value_len, "300") != 0 || + ush_browser_line_has_token_icase(value, value_len, "400") != 0 || + ush_browser_line_has_token_icase(value, value_len, "500") != 0) { + io_delta->set_bold = 1; + io_delta->bold = 0; + } + } else if (ush_browser_css_name_eq(decl + name_start, name_end - name_start, "display") != 0) { + io_delta->set_display_none = 1; + io_delta->display_none = (ush_browser_line_has_token_icase(value, value_len, "none") != 0) ? 1 : 0; + } else if (ush_browser_css_name_eq(decl + name_start, name_end - name_start, "visibility") != 0) { + if (ush_browser_line_has_token_icase(value, value_len, "hidden") != 0) { + io_delta->set_display_none = 1; + io_delta->display_none = 1; + } else if (ush_browser_line_has_token_icase(value, value_len, "visible") != 0) { + io_delta->set_display_none = 1; + io_delta->display_none = 0; } } } @@ -1296,35 +1657,193 @@ static void ush_browser_css_apply_declarations(const char *decl, u64 decl_len, i } } -static int ush_browser_css_selector_targets_anchor(const char *selector, u64 selector_len) { - u64 i; +static int ush_browser_css_is_ident_char(char ch) { + return ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '_') + ? 1 + : 0; +} - if (selector == (const char *)0) { +static int ush_browser_css_capture_ident(const char *text, u64 len, u64 *io_pos, char *out, u64 out_cap) { + u64 pos; + u64 at = 0ULL; + + if (text == (const char *)0 || io_pos == (u64 *)0 || out == (char *)0 || out_cap == 0ULL || *io_pos >= len) { return 0; } - for (i = 0ULL; i < selector_len; i++) { - char ch = ush_browser_ascii_tolower(selector[i]); - if (ch == 'a') { - char prev = (i == 0ULL) ? ' ' : selector[i - 1ULL]; - char next = (i + 1ULL < selector_len) ? selector[i + 1ULL] : ' '; - int prev_ok = (i == 0ULL || prev == ' ' || prev == '\t' || prev == '\r' || prev == '\n' || prev == ',' || - prev == '>' || prev == '+' || prev == '~') - ? 1 - : 0; - int next_ok = (next == ' ' || next == '\t' || next == '\r' || next == '\n' || next == ',' || next == '\0' || - next == '.' || next == '#' || next == ':' || next == '[' || next == '>' || next == '+' || - next == '~') - ? 1 - : 0; - - if (prev_ok != 0 && next_ok != 0) { - return 1; - } - } + pos = *io_pos; + if (ush_browser_css_is_ident_char(text[pos]) == 0) { + return 0; } - return 0; + while (pos < len && ush_browser_css_is_ident_char(text[pos]) != 0) { + if (at + 1ULL >= out_cap) { + return 0; + } + out[at++] = ush_browser_ascii_tolower(text[pos]); + pos++; + } + + out[at] = '\0'; + *io_pos = pos; + return (at > 0ULL) ? 1 : 0; +} + +static int ush_browser_css_parse_simple_selector(const char *selector, u64 selector_len, ush_browser_css_rule *out_rule) { + u64 pos = 0ULL; + int matched = 0; + + if (selector == (const char *)0 || out_rule == (ush_browser_css_rule *)0 || selector_len == 0ULL) { + return 0; + } + + ush_zero(out_rule, (u64)sizeof(*out_rule)); + + while (pos < selector_len && ush_browser_css_is_space(selector[pos]) != 0) { + pos++; + } + + while (pos < selector_len) { + char ch = selector[pos]; + if (ch == ':' || ch == '[') { + break; + } + if (ch == '#') { + pos++; + if (out_rule->id_name[0] == '\0' && + ush_browser_css_capture_ident(selector, selector_len, &pos, out_rule->id_name, (u64)sizeof(out_rule->id_name)) != + 0) { + matched = 1; + continue; + } + while (pos < selector_len && ush_browser_css_is_ident_char(selector[pos]) != 0) { + pos++; + } + continue; + } + if (ch == '.') { + pos++; + if (out_rule->class_name[0] == '\0' && ush_browser_css_capture_ident(selector, selector_len, &pos, + out_rule->class_name, + (u64)sizeof(out_rule->class_name)) != 0) { + matched = 1; + continue; + } + while (pos < selector_len && ush_browser_css_is_ident_char(selector[pos]) != 0) { + pos++; + } + continue; + } + if (ush_browser_css_is_ident_char(ch) != 0) { + if (out_rule->tag[0] == '\0' && + ush_browser_css_capture_ident(selector, selector_len, &pos, out_rule->tag, (u64)sizeof(out_rule->tag)) != 0) { + matched = 1; + continue; + } + while (pos < selector_len && ush_browser_css_is_ident_char(selector[pos]) != 0) { + pos++; + } + continue; + } + if (ch == '*') { + matched = 1; + pos++; + continue; + } + + pos++; + } + + return matched; +} + +static void ush_browser_css_add_rule(const ush_browser_css_rule *rule) { + if (rule == (const ush_browser_css_rule *)0 || ush_browser_css_rule_count >= (u64)USH_BROWSER_CSS_RULE_MAX) { + return; + } + + ush_browser_css_rules[ush_browser_css_rule_count++] = *rule; +} + +static int ush_browser_css_is_selector_separator(char ch) { + return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '>' || ch == '+' || ch == '~') ? 1 : 0; +} + +static void ush_browser_css_parse_selector_list(const char *selector_text, u64 selector_len, const ush_browser_style_delta *delta) { + u64 part_start = 0ULL; + u64 i; + + if (selector_text == (const char *)0 || delta == (const ush_browser_style_delta *)0) { + return; + } + + for (i = 0ULL; i <= selector_len; i++) { + if (i == selector_len || selector_text[i] == ',') { + u64 start = part_start; + u64 end = i; + u64 compound_start; + u64 compound_end; + ush_browser_css_rule rule; + + while (start < end && ush_browser_css_is_space(selector_text[start]) != 0) { + start++; + } + while (end > start && ush_browser_css_is_space(selector_text[end - 1ULL]) != 0) { + end--; + } + + if (end > start) { + compound_end = end; + compound_start = compound_end; + + while (compound_start > start && ush_browser_css_is_selector_separator(selector_text[compound_start - 1ULL]) == 0) { + compound_start--; + } + while (compound_start < compound_end && ush_browser_css_is_selector_separator(selector_text[compound_start]) != 0) { + compound_start++; + } + + if (compound_end > compound_start && + ush_browser_css_parse_simple_selector(selector_text + compound_start, compound_end - compound_start, &rule) != + 0) { + rule.delta = *delta; + ush_browser_css_add_rule(&rule); + } + } + + part_start = i + 1ULL; + } + } +} + +static void ush_browser_css_skip_ws_and_comments(const char *text, u64 len, u64 *io_pos) { + u64 pos; + + if (text == (const char *)0 || io_pos == (u64 *)0 || *io_pos > len) { + return; + } + + pos = *io_pos; + for (;;) { + while (pos < len && ush_browser_css_is_space(text[pos]) != 0) { + pos++; + } + if (pos + 1ULL < len && text[pos] == '/' && text[pos + 1ULL] == '*') { + pos += 2ULL; + while (pos + 1ULL < len && !(text[pos] == '*' && text[pos + 1ULL] == '/')) { + pos++; + } + if (pos + 1ULL < len) { + pos += 2ULL; + } else { + pos = len; + } + continue; + } + break; + } + + *io_pos = pos; } static void ush_browser_css_parse_stylesheet(const char *css_text) { @@ -1337,31 +1856,36 @@ static void ush_browser_css_parse_stylesheet(const char *css_text) { css_len = ush_strlen(css_text); while (pos < css_len) { - u64 selector_start = pos; + u64 selector_start; u64 selector_end; u64 decl_start; u64 decl_end; + ush_browser_style_delta delta; + ush_browser_css_skip_ws_and_comments(css_text, css_len, &pos); + if (pos >= css_len) { + break; + } + + selector_start = pos; while (pos < css_len && css_text[pos] != '{') { pos++; } if (pos >= css_len) { break; } - selector_end = pos; pos++; + decl_start = pos; while (pos < css_len && css_text[pos] != '}') { pos++; } decl_end = pos; - if (selector_end > selector_start && - ush_browser_css_selector_targets_anchor(css_text + selector_start, selector_end - selector_start) != 0) { - ush_browser_css_apply_declarations(css_text + decl_start, decl_end - decl_start, &ush_browser_css_link_blue, - &ush_browser_css_link_underline); - } + ush_browser_style_delta_reset(&delta); + ush_browser_css_apply_declarations(css_text + decl_start, decl_end - decl_start, &delta); + ush_browser_css_parse_selector_list(css_text + selector_start, selector_end - selector_start, &delta); if (pos < css_len && css_text[pos] == '}') { pos++; @@ -1425,6 +1949,120 @@ static void ush_browser_css_scan_style_nodes(GumboNode *node) { } } +static int ush_browser_css_attr_eq_icase(const char *left, const char *right) { + u64 i = 0ULL; + + if (left == (const char *)0 || right == (const char *)0) { + return 0; + } + + while (left[i] != '\0' && right[i] != '\0') { + if (ush_browser_ascii_tolower(left[i]) != ush_browser_ascii_tolower(right[i])) { + return 0; + } + i++; + } + + return (left[i] == '\0' && right[i] == '\0') ? 1 : 0; +} + +static int ush_browser_css_class_contains(const char *classes, const char *needle) { + u64 pos = 0ULL; + u64 needle_len; + + if (classes == (const char *)0 || needle == (const char *)0 || needle[0] == '\0') { + return 0; + } + + needle_len = ush_strlen(needle); + while (classes[pos] != '\0') { + u64 start; + u64 end; + u64 len; + u64 i; + int same = 1; + + while (classes[pos] != '\0' && ush_browser_css_is_space(classes[pos]) != 0) { + pos++; + } + if (classes[pos] == '\0') { + break; + } + + start = pos; + while (classes[pos] != '\0' && ush_browser_css_is_space(classes[pos]) == 0) { + pos++; + } + end = pos; + len = end - start; + if (len != needle_len) { + continue; + } + + for (i = 0ULL; i < len; i++) { + if (ush_browser_ascii_tolower(classes[start + i]) != ush_browser_ascii_tolower(needle[i])) { + same = 0; + break; + } + } + + if (same != 0) { + return 1; + } + } + + return 0; +} + +static int ush_browser_css_rule_matches_node(const ush_browser_css_rule *rule, GumboNode *node) { + const char *tag_name; + GumboAttribute *class_attr; + GumboAttribute *id_attr; + + if (rule == (const ush_browser_css_rule *)0 || node == (GumboNode *)0 || node->type != GUMBO_NODE_ELEMENT) { + return 0; + } + + tag_name = gumbo_normalized_tagname(node->v.element.tag); + if (rule->tag[0] != '\0') { + if (tag_name == (const char *)0 || ush_browser_css_attr_eq_icase(tag_name, rule->tag) == 0) { + return 0; + } + } + + if (rule->class_name[0] != '\0') { + class_attr = gumbo_get_attribute(&node->v.element.attributes, "class"); + if (class_attr == (GumboAttribute *)0 || class_attr->value == (const char *)0 || + ush_browser_css_class_contains(class_attr->value, rule->class_name) == 0) { + return 0; + } + } + + if (rule->id_name[0] != '\0') { + id_attr = gumbo_get_attribute(&node->v.element.attributes, "id"); + if (id_attr == (GumboAttribute *)0 || id_attr->value == (const char *)0 || + ush_browser_css_attr_eq_icase(id_attr->value, rule->id_name) == 0) { + return 0; + } + } + + return 1; +} + +static void ush_browser_css_apply_rules_for_node(GumboNode *node, ush_browser_style *io_style) { + u64 i; + + if (node == (GumboNode *)0 || io_style == (ush_browser_style *)0 || node->type != GUMBO_NODE_ELEMENT) { + return; + } + + for (i = 0ULL; i < ush_browser_css_rule_count; i++) { + if (ush_browser_css_rule_matches_node(&ush_browser_css_rules[i], node) != 0) { + ush_browser_style_apply_delta(io_style, &ush_browser_css_rules[i].delta); + } + } +} + static int ush_browser_is_skip_tag(GumboTag tag) { switch (tag) { case GUMBO_TAG_SCRIPT: @@ -1578,8 +2216,8 @@ static int ush_browser_find_title_node(GumboNode *node, char *out, u64 out_cap) return 0; } -static void ush_browser_walk_dom(GumboNode *node) { - if (node == (GumboNode *)0) { +static void ush_browser_walk_dom_styled(GumboNode *node, const ush_browser_style *parent_style) { + if (node == (GumboNode *)0 || parent_style == (const ush_browser_style *)0) { return; } @@ -1587,27 +2225,51 @@ static void ush_browser_walk_dom(GumboNode *node) { case GUMBO_NODE_TEXT: case GUMBO_NODE_WHITESPACE: case GUMBO_NODE_CDATA: - ush_browser_text_append(node->v.text.text); + if (parent_style->display_none == 0) { + ush_browser_text_append(node->v.text.text); + } return; + case GUMBO_NODE_ELEMENT: case GUMBO_NODE_TEMPLATE: { GumboTag tag = node->v.element.tag; GumboVector *children = &node->v.element.children; + GumboAttribute *style_attr; + ush_browser_style style = *parent_style; + ush_browser_style_delta inline_delta; int is_block = ush_browser_is_block_tag(tag); - int is_anchor = (tag == GUMBO_TAG_A) ? 1 : 0; - int link_blue = ush_browser_css_link_blue; - int link_underline = ush_browser_css_link_underline; + int style_changed; u64 i; if (ush_browser_is_skip_tag(tag) != 0) { return; } + if (tag == GUMBO_TAG_A) { + ush_browser_collect_anchor_link(node); + ush_browser_style_apply_anchor_default(&style); + } + + ush_browser_css_apply_rules_for_node(node, &style); + + style_attr = gumbo_get_attribute(&node->v.element.attributes, "style"); + if (style_attr != (GumboAttribute *)0 && style_attr->value != (const char *)0) { + ush_browser_style_delta_reset(&inline_delta); + ush_browser_css_apply_declarations(style_attr->value, ush_strlen(style_attr->value), &inline_delta); + ush_browser_style_apply_delta(&style, &inline_delta); + } + + if (style.display_none != 0) { + return; + } + if (tag == GUMBO_TAG_BR || tag == GUMBO_TAG_HR) { ush_browser_text_newline(); return; } + style_changed = (ush_browser_style_equal(&style, parent_style) == 0) ? 1 : 0; + if (is_block != 0) { ush_browser_text_newline(); if (tag == GUMBO_TAG_LI) { @@ -1615,24 +2277,16 @@ static void ush_browser_walk_dom(GumboNode *node) { } } - if (is_anchor != 0) { - GumboAttribute *style_attr; - - ush_browser_collect_anchor_link(node); - style_attr = gumbo_get_attribute(&node->v.element.attributes, "style"); - if (style_attr != (GumboAttribute *)0 && style_attr->value != (const char *)0) { - ush_browser_css_apply_declarations(style_attr->value, ush_strlen(style_attr->value), &link_blue, - &link_underline); - } - ush_browser_text_begin_link_style(link_blue, link_underline); + if (style_changed != 0) { + ush_browser_text_emit_style(&style); } for (i = 0ULL; i < (u64)children->length; i++) { - ush_browser_walk_dom((GumboNode *)children->data[i]); + ush_browser_walk_dom_styled((GumboNode *)children->data[i], &style); } - if (is_anchor != 0) { - ush_browser_text_end_link_style(); + if (style_changed != 0) { + ush_browser_text_emit_style(parent_style); } if (is_block != 0) { @@ -1640,15 +2294,21 @@ static void ush_browser_walk_dom(GumboNode *node) { } return; } + case GUMBO_NODE_DOCUMENT: { GumboVector *children = &node->v.document.children; u64 i; + if (parent_style->display_none != 0) { + return; + } + for (i = 0ULL; i < (u64)children->length; i++) { - ush_browser_walk_dom((GumboNode *)children->data[i]); + ush_browser_walk_dom_styled((GumboNode *)children->data[i], parent_style); } return; } + default: return; } @@ -1700,6 +2360,7 @@ static int ush_browser_read_file(const ush_state *sh, const char *arg, char *out static int ush_browser_render_html(const char *html, u64 html_size) { GumboOutput *output; + ush_browser_style root_style; if (html == (const char *)0 || html_size == 0ULL) { return 0; @@ -1711,14 +2372,14 @@ static int ush_browser_render_html(const char *html, u64 html_size) { } ush_browser_link_count = 0ULL; + ush_browser_css_rule_count = 0ULL; ush_browser_text_reset(); ush_zero(ush_browser_title, (u64)sizeof(ush_browser_title)); - ush_browser_css_link_blue = 1; - ush_browser_css_link_underline = 1; + ush_browser_style_reset(&root_style); (void)ush_browser_find_title_node(output->root, ush_browser_title, (u64)sizeof(ush_browser_title)); ush_browser_css_scan_style_nodes(output->root); - ush_browser_walk_dom(output->root); + ush_browser_walk_dom_styled(output->root, &root_style); ush_browser_text_trim_trailing_spaces(); gumbo_destroy_output(&kGumboDefaultOptions, output); diff --git a/clks b/clks index 3afcafa..9097e63 160000 --- a/clks +++ b/clks @@ -1 +1 @@ -Subproject commit 3afcafa11b7f24c61a1f351499f4b8461f97b2c9 +Subproject commit 9097e6306bcc3128935f71eae367a0830556016e