diff --git a/src/servo/content.rs b/src/servo/content.rs index 8cbbc0662b7..0ea22d036a2 100644 --- a/src/servo/content.rs +++ b/src/servo/content.rs @@ -3,6 +3,7 @@ export content; import result::extensions; import dom::rcu::writer_methods; +import dom::style; import dom=dom::base; import layout::layout; import js::rust::methods; @@ -38,11 +39,36 @@ fn content(to_layout: chan) -> chan { parse(filename) { #debug["content: Received filename `%s` to parse", filename]; + // TODO actually parse where the css sheet should be + // Replace .html with .css and try to open a stylesheet + assert filename.ends_with(".html"); + let new_file = filename.substr(0u, filename.len() - 5u) + + ".css"; + + // Send off a task to parse the stylesheet + let css_port = comm::port(); + let css_chan = comm::chan(css_port); + task::spawn {|| + let css_stream = parser::lexer:: + spawn_css_parser_task(new_file); + let css_rules = parser::css_builder:: + build_stylesheet(css_stream); + css_chan.send(css_rules); + }; + // Note: we can parse the next document in parallel // with any previous documents. - let stream = lexer::spawn_html_parser_task(filename); + let stream = parser::lexer::spawn_html_parser_task(filename); let root = parser::html_builder::build_dom(scope, stream); + // Collect the css stylesheet + let css_rules = comm::recv(css_port); + + // Apply the css rules to the dom tree: + // TODO + #debug["%s",style::print_sheet(css_rules)]; + + // Now, join the layout so that they will see the latest // changes we have made. join_layout(scope, to_layout); diff --git a/src/servo/dom/style.rs b/src/servo/dom/style.rs index 3095e5aebc9..10e1019ddec 100644 --- a/src/servo/dom/style.rs +++ b/src/servo/dom/style.rs @@ -7,7 +7,7 @@ enum display_type{ } enum style_decl{ - font_size(uint), + font_size(uint), // Currently assumes format '# pt' display(display_type), text_color(uint), background_color(uint) @@ -27,10 +27,9 @@ enum selector{ sibling(~selector, ~selector) } -type rule = (selector, [style_decl]); - -type stylesheet = [rule]; +type rule = ([~selector], [style_decl]); +type stylesheet = [~rule]; fn print_list(list : [T], print : fn(T) -> str) -> str { let l = vec::len(list); @@ -48,6 +47,23 @@ fn print_list(list : [T], print : fn(T) -> str) -> str { ret res; } +fn print_list_vert(list : [T], print : fn(T) -> str) -> str { + let l = vec::len(list); + if l == 0u { ret "" } + + let mut res = "-"; + res += print(list[0]); + let mut i = 1u; + + while i < l { + res += "\n-"; + res += print(list[i]); + i += 1u; + } + + ret res; +} + fn print_display(dis_ty : display_type) -> str { alt dis_ty { block { "block" } @@ -57,7 +73,7 @@ fn print_display(dis_ty : display_type) -> str { fn print_style(decl : style_decl) -> str{ alt decl { - font_size(s) { #fmt("Font size = %u px", s) } + font_size(s) { #fmt("Font size = %u pt", s) } display(dis_ty) { #fmt("Display style = %s", print_display(dis_ty)) } text_color(c) { #fmt("Text color = 0x%06x", c) } background_color(c) { #fmt("Background color = 0x%06x", c) } @@ -73,7 +89,7 @@ fn print_attr(attribute : attr) -> str { } } -fn print_selector(select : ~selector) -> str { +fn print_selector(&&select : ~selector) -> str { alt *select { element(s, attrs) { #fmt("Element %s with attributes: %s", s, print_list(attrs, print_attr)) } @@ -86,39 +102,39 @@ fn print_selector(select : ~selector) -> str { } } -fn print_rule(rule : rule) -> str { - alt rule { - (sel, styles) { - let sel_str = print_selector(~(copy sel)); +fn print_rule(&&rule : ~rule) -> str { + alt *rule { + (sels, styles) { + let sel_str = print_list(sels, print_selector); let sty_str = print_list(styles, print_style); - #fmt("Selector: %s, Style: {%s}", sel_str, sty_str) + #fmt("Selectors: %s; Style: {%s}", sel_str, sty_str) } } } fn print_sheet(sheet : stylesheet) -> str { - #fmt("CSS Rules: %s", print_list(sheet, print_rule)) + #fmt("CSS Rules:\n%s", print_list_vert(sheet, print_rule)) } #[test] fn test_pretty_print() { - let test1 = [(element("p", []), [font_size(32u)])]; + let test1 = [~([~element("p", [])], [font_size(32u)])]; let actual1 = print_sheet(test1); - let expected1 = "CSS Rules: Selector: Element p with attributes: ," + - " Style: {Font size = 32 px}"; + let expected1 = "CSS Rules:\n-Selectors: Element p with attributes: ;" + + " Style: {Font size = 32 pt}"; assert(actual1 == expected1); let elmt1 = ~element("*", []); let elmt2 = ~element("body", [exact("class", "2")]); - let test2 = [(descendant(elmt1, elmt2), + let test2 = [~([~descendant(elmt1, elmt2)], [display(block), text_color(0u)])]; let actual2 = print_sheet(test2); - let expected2 = "CSS Rules: Selector: (Element * with attributes: ) " + - "(Element body with attributes: [class = 2]), " + + let expected2 = "CSS Rules:\n-Selectors: (Element * with attributes: ) " + + "(Element body with attributes: [class = 2]); " + "Style: {Display style = block, Text color = 0x000000}"; assert(actual2 == expected2); diff --git a/src/servo/layout/base.rs b/src/servo/layout/base.rs index eb6f22a8f44..f60ac46b239 100644 --- a/src/servo/layout/base.rs +++ b/src/servo/layout/base.rs @@ -165,7 +165,7 @@ mod test { for tree::each_child(btree, root) {|c| r += flat_bounds(c); } - ret r + [root.bounds]; + ret r + [copy root.bounds]; } #[test] diff --git a/src/servo/parser/css_builder.rs b/src/servo/parser/css_builder.rs new file mode 100644 index 00000000000..cc74afb5f5d --- /dev/null +++ b/src/servo/parser/css_builder.rs @@ -0,0 +1,262 @@ +#[doc="Constructs a list of style rules from a token stream"] + +// TODO: fail according to the css spec instead of failing when things +// are not as expected + +import dom::style::*; +import parser::lexer::css::{token, to_start_desc, to_end_desc, + to_descendant, to_child, to_sibling, + to_comma, to_elmt, to_attr, to_desc, + to_eof}; +import comm::recv; + +type token_reader = {stream : port, mut lookahead : option}; + +impl methods for token_reader { + fn get() -> token { + alt self.lookahead { + some(tok) { self.lookahead = none; tok } + none { recv(self.stream) } + } + } + + fn unget(tok : token) { + self.lookahead = some(tok); + } +} + +fn parse_element(reader : token_reader) -> option<~selector> { + // Get the current element type + let elmt_name = alt reader.get() { + to_elmt(tag) { tag } + to_eof { ret none; } + _ { fail "Expected an element" } + }; + + let mut attr_list = []; + + // Get the attributes associated with that element + loop { + let tok = reader.get(); + alt tok { + to_attr(attr) { attr_list += [attr]; } + to_start_desc | to_descendant | to_child | to_sibling + | to_comma { + reader.unget(tok); + break; + } + to_eof { ret none; } + to_elmt(_) { fail "Unexpected second element without " + + "relation to first element"; } + to_end_desc { fail "Unexpected '}'"; } + to_desc(_, _) { fail "Unexpected description"; } + } + } + + ret some(~element(elmt_name, attr_list)); +} + +// Currently colors are supported in rgb(a,b,c) form and also by +// keywords for several common colors. +// TODO: extend this +fn parse_color(color : str) -> uint { + let blue_unit = 1u; + let green_unit = 256u; + let red_unit = 256u * 256u; + + let result_color = if color.starts_with("rgb(") { + let color_vec = str::bytes(color); + let mut i = 4u; + let mut red_vec = []; + let mut green_vec = []; + let mut blue_vec = []; + + while i < color_vec.len() && color_vec[i] != ',' as u8 { + red_vec += [color_vec[i]]; + i += 1u; + } + + i += 1u; + + while i < color_vec.len() && color_vec[i] != ',' as u8 { + green_vec += [color_vec[i]]; + i += 1u; + } + + i += 1u; + + while i < color_vec.len() && color_vec[i] != ',' as u8 { + blue_vec += [color_vec[i]]; + i += 1u; + } + + // TODO, fail by ignoring the rule instead of setting the + // color to black + + let blue_intense = alt uint::from_str(str::from_bytes(blue_vec)) { + some(c) { c } + none { 0u } + }; + + let green_intense = alt uint::from_str(str::from_bytes(green_vec)) { + some(c) { c } + none { 0u } + }; + + let red_intense = alt uint::from_str(str::from_bytes(red_vec)) { + some(c) { c } + none { 0u } + }; + + + blue_unit * blue_intense + green_intense * green_unit + + red_intense * red_unit + } else { + alt color { + "red" { red_unit * 255u } + "blue" { blue_unit * 255u } + "green" { green_unit * 255u} + "white" { red_unit * 256u - 1u } + "black" { 0u } + // TODO, fail by ignoring the rule instead of setting the + // color to black + _ { #debug["Unrecognized color %s", color]; 0u } + } + }; + + ret result_color; +} + +fn parse_rule(reader : token_reader) -> option<~rule> { + let mut sel_list = []; + let mut desc_list = []; + + // Collect all the selectors that this rule applies to + loop { + let mut cur_sel; + + alt parse_element(reader) { + some(elmt) { cur_sel <- elmt; } + none { ret none; } // we hit an eof in the middle of a rule + } + + loop { + let tok = reader.get(); + alt tok { + to_descendant { + alt parse_element(reader) { + some(elmt) { + let built_sel <- cur_sel; + let new_sel <- elmt; + cur_sel <- ~descendant(built_sel, new_sel) + } + none { ret none; } + } + } + to_child { + alt parse_element(reader) { + some(elmt) { + let built_sel <- cur_sel; + let new_sel <- elmt; + cur_sel <- ~child(built_sel, new_sel) + } + none { ret none; } + } + } + to_sibling { + alt parse_element(reader) { + some(elmt) { + let built_sel <- cur_sel; + let new_sel <- elmt; + cur_sel <- ~sibling(built_sel, new_sel) + } + none { ret none; } + } + } + to_start_desc { + let built_sel <- cur_sel; + sel_list += [built_sel]; + reader.unget(to_start_desc); + break; + } + to_comma { + let built_sel <- cur_sel; + sel_list += [built_sel]; + reader.unget(to_comma); + break; + } + to_attr(_) | to_end_desc | to_elmt(_) | to_desc(_, _) { + fail #fmt["Unexpected token %? in elements", tok]; + } + to_eof { ret none; } + } + } + + // check if we should break out of the nesting loop as well + let tok = reader.get(); + alt tok { + to_start_desc { break; } + to_comma { } + _ { reader.unget(tok); } + } + } + + // Get the description to be applied to the selector + loop { + let tok = reader.get(); + alt tok { + to_end_desc { break; } + to_desc(prop, val) { + alt prop { + "font-size" { + // TODO, support more ways to declare a font size than # pt + assert val.ends_with("pt"); + let num = val.substr(0u, val.len() - 2u); + + alt uint::from_str(num) { + some(n) { desc_list += [font_size(n)]; } + none { fail "Nonnumber provided as font size"; } + } + } + "display" { + alt val { + "inline" { desc_list += [display(inline)]; } + "block" { desc_list += [display(block)]; } + _ { #debug["Recieved unknown display value '%s'", + val]; } + } + } + "color" { + desc_list += [text_color(parse_color(val))]; + } + "background-color" { + desc_list += [background_color(parse_color(val))]; + } + _ { #debug["Recieved unknown style property '%s'", + val]; } + } + } + to_eof { ret none; } + to_start_desc | to_descendant | to_child | to_sibling + | to_comma | to_elmt(_) | to_attr(_) { + fail #fmt["Unexpected token %? in description", tok]; + } + } + } + + ret some(~(sel_list, desc_list)); +} + +fn build_stylesheet(stream : port) -> [~rule] { + let mut rule_list = []; + let reader = {stream : stream, mut lookahead : none}; + + loop { + alt parse_rule(reader) { + some(rule) { let r <- rule; rule_list += [r]; } + none { break; } + } + } + + ret rule_list; +} diff --git a/src/servo/parser/lexer.rs b/src/servo/parser/lexer.rs index 207ce9566c8..e68a5ef3695 100644 --- a/src/servo/parser/lexer.rs +++ b/src/servo/parser/lexer.rs @@ -307,7 +307,7 @@ mod css { '>' as u8 { to_child } '+' as u8 { to_sibling } ',' as u8 { to_comma } - _ { to_descendant } + _ { self.unget(c); to_descendant } }; self.eat_whitespace(); @@ -316,14 +316,22 @@ mod css { } fn parse_css_element(c : u8) -> token { - /* Check for special attributes with an implied element.*/ + assert self.lookahead.is_none(); + + /* Check for special attributes with an implied element, + or a wildcard which is not a alphabet character.*/ if c == '.' as u8 || c == '#' as u8 { self.state = ps_css_attribute; self.unget(c); ret to_elmt("*"); + } else if c == '*' as u8 { + self.state = ps_css_attribute; + ret to_elmt("*"); } + self.unget(c); let element = self.parse_ident(); + self.state = ps_css_attribute; ret to_elmt(element); @@ -389,7 +397,11 @@ mod css { fn parse_css_description(c: u8) -> token { let mut ch = c; - if ch.is_whitespace() { + if ch == '}' as u8 { + self.state = ps_css_elmt; + self.eat_whitespace(); + ret to_end_desc; + } else if ch.is_whitespace() { self.eat_whitespace(); alt self.get() { @@ -439,7 +451,7 @@ mod css { if desc_val.len() == 0u { fail "Expected descriptor value"; } else { - self.state = ps_css_elmt; + self.unget('}' as u8); break; } } else if ch == ';' as u8 { @@ -485,16 +497,29 @@ fn spawn_css_parser_task(filename: str) -> port { let result_port = port(); let result_chan = chan(result_port); task::spawn {|| - let file_data = io::read_whole_file(filename).get(); - let reader = io::bytes_reader(file_data); - assert filename.ends_with(".css"); - let parser : parser = parser(reader, ps_css_elmt); - loop { - let token = parser.parse_css(); - result_chan.send(token); - if token == css::to_eof { break; } + let file_try = io::read_whole_file(filename); + + // Check if the given css file existed, if it does, parse it, + // otherwise just send an eof. This is a hack to allow + // guessing that if foo.html exists, foo.css is the + // corresponding stylesheet. + if file_try.is_success() { + #debug["Lexing css sheet %s", filename]; + let file_data = file_try.get(); + let reader = io::bytes_reader(file_data); + + let parser : parser = parser(reader, ps_css_elmt); + + loop { + let token = parser.parse_css(); + result_chan.send(token); + if token == css::to_eof { break; } + } + } else { + #debug["Failed to open css sheet %s", filename]; + result_chan.send(css::to_eof); } }; ret result_port; diff --git a/src/servo/servo.rc b/src/servo/servo.rc index a773e46a160..2ea68868145 100755 --- a/src/servo/servo.rc +++ b/src/servo/servo.rc @@ -51,6 +51,7 @@ mod layout { mod parser { mod lexer; mod html_builder; + mod css_builder; } mod platform { diff --git a/src/servo/servo.rs b/src/servo/servo.rs index e7607e55f83..71d6697fcd9 100644 --- a/src/servo/servo.rs +++ b/src/servo/servo.rs @@ -1,6 +1,5 @@ import comm::*; import parser::lexer; -//import parser::lexer::util_methods; import result::extensions; import gfx::renderer; import platform::osmain; diff --git a/src/test/test.css b/src/test/test.css new file mode 100644 index 00000000000..f2094480d9e --- /dev/null +++ b/src/test/test.css @@ -0,0 +1,5 @@ +p {font-size : 12pt} +p img {color : rgb(0,255,0); display : block } +p.blue > p.green + p.red { background-color : blue ;color : green } +img[class] .pastoral *[lang|=en] { display:inline} +.book > #novel + *[type=novella] p { color : blue; color : white }