Added css selector matching and now render specified background colors

added comments and put back random colors for unspecified boxes
This commit is contained in:
Margaret Meyerhofer 2012-06-04 10:25:15 -07:00
parent 10294134ab
commit e3d9650196
14 changed files with 509 additions and 100 deletions

View file

@ -50,7 +50,7 @@ fn content(to_layout: chan<layout::msg>) -> chan<msg> {
let css_chan = comm::chan(css_port);
task::spawn {||
let css_stream = parser::lexer::
spawn_css_parser_task(new_file);
spawn_css_lexer_task(new_file);
let css_rules = parser::css_builder::
build_stylesheet(css_stream);
css_chan.send(css_rules);
@ -74,7 +74,7 @@ fn content(to_layout: chan<layout::msg>) -> chan<msg> {
join_layout(scope, to_layout);
// Send new document to layout.
to_layout.send(layout::build(root));
to_layout.send(layout::build(root, css_rules));
// Indicate that reader was forked so any further
// changes will be isolated.

View file

@ -2,8 +2,9 @@ import io::println;
enum display_type{
block,
inline
di_block,
di_inline,
di_none
}
enum style_decl{
@ -66,8 +67,9 @@ fn print_list_vert<T>(list : [T], print : fn(T) -> str) -> str {
fn print_display(dis_ty : display_type) -> str {
alt dis_ty {
block { "block" }
inline { "inline" }
di_block { "block" }
di_inline { "inline" }
di_none { "none" }
}
}
@ -130,7 +132,7 @@ fn test_pretty_print() {
let elmt2 = ~element("body", [exact("class", "2")]);
let test2 = [~([~descendant(elmt1, elmt2)],
[display(block), text_color(0u)])];
[display(di_block), text_color(0u)])];
let actual2 = print_sheet(test2);
let expected2 = "CSS Rules:\n-Selectors: (Element * with attributes: ) "

View file

@ -21,9 +21,11 @@ enum box_kind {
class appearance {
let mut background_image: option<@image>;
let mut background_color: option<uint>;
new() {
self.background_image = none;
self.background_color = none;
}
}

View file

@ -1,12 +1,13 @@
#[doc="Creates CSS boxes from a DOM."]
import dom::base::{element, es_div, es_img, nk_element, nk_text, node};
import dom::style::{display_type, di_block, di_inline, di_none};
import dom::rcu::reader_methods;
import gfx::geom;
import /*layout::*/base::{appearance, bk_block, bk_inline, bk_intrinsic};
import /*layout::*/base::{bk_text, box, box_kind, btree, node_methods, ntree};
import /*layout::*/base::{rd_tree_ops, wr_tree_ops};
import /*layout::*/style::style::{di_block, di_inline, style_methods};
import /*layout::*/style::style::{style_methods};
import /*layout::*/text::text_box;
import util::tree;
@ -62,7 +63,9 @@ impl methods for ctxt {
// Add the child's box to the current enclosing box or the current
// anonymous box.
alt kid.get_computed_style().display {
di_block { btree.add_child(self.parent_box, kid_box); }
di_block {
btree.add_child(self.parent_box, kid_box);
}
di_inline {
let anon_box = alt self.anon_box {
none {

View file

@ -11,6 +11,7 @@ import gfx::geom;
import gfx::renderer;
import dom::base::node;
import dom::rcu::scope;
import dom::style::stylesheet;
import /*layout::*/base::*;
import /*layout::*/style::apply::apply_style_methods;
import /*layout::*/style::style::style_methods;
@ -18,7 +19,7 @@ import box_builder::box_builder_methods;
import dl = display_list;
enum msg {
build(node),
build(node, stylesheet),
ping(chan<content::ping>),
exit
}
@ -29,16 +30,16 @@ fn layout(to_renderer: chan<renderer::msg>) -> chan<msg> {
alt po.recv() {
ping(ch) { ch.send(content::pong); }
exit { break; }
build(node) {
build(node, styles) {
#debug("layout: received layout request for:");
node.dump();
node.recompute_style_for_subtree();
node.recompute_style_for_subtree(styles);
let this_box = node.construct_boxes();
this_box.dump();
this_box.apply_style_for_subtree();
this_box.apply_style_for_subtree();
this_box.reflow(geom::px_to_au(800));
let dlist = build_display_list(this_box);
@ -61,25 +62,41 @@ fn build_display_list(box: @base::box) -> display_list::display_list {
}
fn box_to_display_item(box: @base::box) -> dl::display_item {
let mut item;
alt box.appearance.background_image {
some(image) {
item = dl::display_item({
item_type: dl::display_item_image(~copy *image),
bounds: copy box.bounds
});
}
none {
let r = rand::rng();
item = dl::display_item({
item_type: dl::display_item_solid_color(r.next() as u8,
r.next() as u8,
r.next() as u8),
bounds: copy box.bounds
});
}
}
let mut item;
alt box.appearance.background_image {
some(image) {
item = dl::display_item({
item_type: dl::display_item_image(~copy *image),
bounds: copy box.bounds
});
}
none {
alt box.appearance.background_color {
some(col) {
let red_col = (col >> 16u) & 255u;
let green_col = (col >> 8u) & 255u;
let blue_col = col & 255u;
#debug("layout: display item: %?", item);
item = dl::display_item({
item_type: dl::display_item_solid_color(red_col as u8,
green_col as u8,
blue_col as u8),
bounds: copy box.bounds
});
}
none {
let r = rand::rng();
item = dl::display_item({
item_type: dl::display_item_solid_color(r.next() as u8,
r.next() as u8,
r.next() as u8),
bounds: copy box.bounds
});
}
}
}
}
#debug("layout: display item: %?", item);
ret item;
}

View file

@ -3,46 +3,51 @@ import dom::base::{es_img, nk_element, node};
import dom::rcu::reader_methods;
import image::base::load;
import /*layout::*/base::*;
import style::style_methods;
impl apply_style_methods for @box {
fn apply_style_for_subtree() {
self.apply_style();
for btree.each_child(self) {
|child|
child.apply_style_for_subtree();
}
}
fn apply_style_for_subtree() {
self.apply_style();
for btree.each_child(self) {
|child|
child.apply_style_for_subtree();
}
}
#[doc="Applies CSS style."]
fn apply_style() {
// Right now, we only handle images.
self.node.rd {
|node|
alt node.kind {
~nk_element(element) {
alt element.subclass {
~es_img(*) {
alt element.get_attr("src") {
some(url) {
// FIXME: Some sort of BASE HREF support!
// FIXME: Parse URLs!
// FIXME: Don't load synchronously!
#debug("loading image from %s", url);
let image = @load(url);
self.appearance.background_image =
some(image);
}
none {
/* Ignore. */
}
}
}
_ { /* Ignore. */ }
}
}
_ { /* Ignore. */ }
}
}
}
#[doc="Applies CSS style."]
fn apply_style() {
// Right now, we only handle images.
self.node.rd {
|node|
alt node.kind {
~nk_element(element) {
let style = self.node.get_computed_style();
self.appearance.background_color = some(style.back_color);
alt element.subclass {
~es_img(*) {
alt element.get_attr("src") {
some(url) {
// FIXME: Some sort of BASE HREF support!
// FIXME: Parse URLs!
// FIXME: Don't load synchronously!
#debug("loading image from %s", url);
let image = @load(url);
self.appearance.background_image =
some(image);
}
none {
/* Ignore. */
}
}
}
_ { /* Ignore. */ }
}
}
_ { /* Ignore. */ }
}
}
}
}

View file

@ -0,0 +1,339 @@
#[doc="Perform css selector matching"]
import dom::base::{node, nk_element, nk_text};
import dom::style::{selector, style_decl, font_size, display, text_color,
background_color, stylesheet, element, child, descendant,
sibling, attr, exact, exists, includes, starts_with};
import dom::rcu::{reader_methods};
import style::{computed_style, default_style_for_node_kind};
import base::{layout_data};
export matching_methods;
#[doc="Update the computed style of an html element with a style specified
by css."]
fn update_style(style : @computed_style, decl : style_decl) {
alt decl {
display(dis) { (*style).display = dis; }
background_color(col) { (*style).back_color = col; }
text_color(*) | font_size(*) { /* not supported yet */ }
}
}
#[doc="Check if a css attribute matches the attribute of an html element."]
fn attrs_match(attr : attr, elmt : dom::base::element) -> bool {
alt attr {
exists(name) {
alt elmt.get_attr(name) {
some(_) { ret true; }
none { ret false; }
}
}
exact(name, val) {
alt elmt.get_attr(name) {
some(value) { ret value == val; }
none { ret false; }
}
}
includes(name, val) {
// Comply with css spec, if the specified attribute is empty
// it cannot match.
if val == "" { ret false; }
alt elmt.get_attr(name) {
some(value) { ret value.split_char(' ').contains(val); }
none { ret false; }
}
}
starts_with(name, val) {
alt elmt.get_attr(name) {
some(value) {
//check that there is only one attribute value and it
//starts with the perscribed value
if !value.starts_with(val) || value.contains(" ") { ret false; }
// We match on either the exact value or value-foo
if value.len() == val.len() { ret true; }
else { ret value.starts_with(val + "-"); }
}
none { ret false; }
}
}
}
}
impl priv_matching_methods for node {
#[doc="Checks if the given css selector, which must describe a single
element with no relational information, describes the given
html element."]
fn matches_element(sel : ~selector) -> bool {
alt *sel {
child(_, _) | descendant(_, _) | sibling(_, _) { ret false; }
element(tag, attrs) {
alt self.rd { |n| copy *n.kind } {
nk_element(elmt) {
if !(tag == "*" || tag == elmt.tag_name) {
ret false;
}
let mut i = 0u;
while i < attrs.len() {
if !attrs_match(attrs[i], elmt) { ret false; }
i += 1u;
}
ret true;
}
nk_text(str) { /*fall through, currently unsupported*/ }
}
}
}
ret false; //If we got this far it was because something was
//unsupported.
}
#[doc = "Checks if a generic css selector matches a given html element"]
fn matches_selector(sel : ~selector) -> bool {
alt *sel {
element(str, atts) { ret self.matches_element(sel); }
child(sel1, sel2) {
alt self.rd { |n| n.tree.parent } {
some(parent) {
ret self.matches_element(sel2) &&
parent.matches_selector(sel1);
}
none { ret false; }
}
}
descendant(sel1, sel2) {
if !self.matches_element(sel2) {
ret false;
}
//loop over all ancestors to check if they are the person
//we should be descended from.
let mut cur_parent = alt self.rd { |n| n.tree.parent } {
some(parent) { parent }
none { ret false; }
};
loop {
if cur_parent.matches_selector(sel1) { ret true; }
cur_parent = alt cur_parent.rd { |n| n.tree.parent } {
some(parent) { parent }
none { ret false; }
};
}
}
sibling(sel1, sel2) {
if !self.matches_element(sel2) { ret false; }
// loop over this node's previous siblings to see if they
// match
alt self.rd { |n| n.tree.prev_sibling } {
some(sib) {
let mut cur_sib = sib;
loop {
if cur_sib.matches_selector(sel1) { ret true; }
cur_sib = alt cur_sib.rd { |n| n.tree.prev_sibling } {
some(sib) { sib }
none { break; }
};
}
}
none { }
}
// check the rest of the siblings
alt self.rd { |n| n.tree.next_sibling } {
some(sib) {
let mut cur_sib = sib;
loop {
if cur_sib.matches_selector(sel1) { ret true; }
cur_sib = alt cur_sib.rd { |n| n.tree.next_sibling } {
some(sib) { sib }
none { break; }
};
}
}
none { }
}
ret false;
}
}
}
}
impl matching_methods for node {
#[doc="Compare an html element to a list of css rules and update its
style according to the rules matching it."]
fn match_css_style(styles : stylesheet) -> computed_style {
let node_kind = self.rd { |n| copy *n.kind };
let style =
@default_style_for_node_kind(node_kind);
// Loop over each rule, see if our node matches what is
// described in the rule. If it matches, update its style.
// As we don't currently have priorities of style information,
// the latest rule takes precedence so we can just overwrite
// style information.
for styles.each { |sty|
let (selectors, decls) <- *(copy sty);
for selectors.each { |sel|
if self.matches_selector(sel) {
#debug("Matched selector {%?} with node {%?}",
*sel, node_kind);
for decls.each { |decl|
update_style(style, decl);
}
}
}
}
#debug["Changed the style to: %?", *style];
ret copy *(style);
}
}
mod test {
import dom::base::{node_scope, methods, nk_element, attr, es_div,
es_img, es_unknown, es_head, wr_tree_ops};
import dvec::{dvec, extensions};
import io::println;
fn new_node_from_attr(scope : node_scope, name : str, val : str) -> node {
let elmt = dom::base::element("div", ~es_div);
let attr = ~attr(name, val);
elmt.attrs.push(copy attr);
ret scope.new_node(nk_element(elmt));
}
#[test]
fn test_match_pipe1() {
let scope = node_scope();
let node = new_node_from_attr(scope, "lang", "en-us");
let sel = element("*", [starts_with("lang", "en")]);
assert node.matches_selector(~sel);
}
#[test]
fn test_match_pipe2() {
let scope = node_scope();
let node = new_node_from_attr(scope, "lang", "en");
let sel = element("*", [starts_with("lang", "en")]);
assert node.matches_selector(~sel);
}
#[test]
fn test_not_match_pipe() {
let scope = node_scope();
let node = new_node_from_attr(scope, "lang", "english");
let sel = element("*", [starts_with("lang", "en")]);
assert !node.matches_selector(~sel);
}
#[test]
fn test_match_includes() {
let scope = node_scope();
let node = new_node_from_attr(scope, "mad", "hatter cobler cooper");
let sel = element("div", [includes("mad", "hatter")]);
assert node.matches_selector(~sel);
}
#[test]
fn test_match_exists() {
let scope = node_scope();
let node = new_node_from_attr(scope, "mad", "hatter cobler cooper");
let sel1 = element("div", [exists("mad")]);
let sel2 = element("div", [exists("hatter")]);
assert node.matches_selector(~sel1);
assert !node.matches_selector(~sel2);
}
#[test]
fn test_match_exact() {
let scope = node_scope();
let node1 = new_node_from_attr(scope, "mad", "hatter cobler cooper");
let node2 = new_node_from_attr(scope, "mad", "hatter");
let sel = element("div", [exact("mad", "hatter")]);
assert !node1.matches_selector(~copy sel);
assert node2.matches_selector(~sel);
}
#[test]
fn match_tree() {
let scope = node_scope();
let root = new_node_from_attr(scope, "class", "blue");
let child1 = new_node_from_attr(scope, "id", "green");
let child2 = new_node_from_attr(scope, "flag", "black");
let gchild = new_node_from_attr(scope, "flag", "grey");
let ggchild = new_node_from_attr(scope, "flag", "white");
let gggchild = new_node_from_attr(scope, "flag", "purple");
scope.add_child(root, child1);
scope.add_child(root, child2);
scope.add_child(child2, gchild);
scope.add_child(gchild, ggchild);
scope.add_child(ggchild, gggchild);
let sel1 = descendant(~element("*", [exact("class", "blue")]),
~element("*", []));
assert !root.matches_selector(~copy sel1);
assert child1.matches_selector(~copy sel1);
assert child2.matches_selector(~copy sel1);
assert gchild.matches_selector(~copy sel1);
assert ggchild.matches_selector(~copy sel1);
assert gggchild.matches_selector(~sel1);
let sel2 = descendant(~child(~element("*", [exact("class", "blue")]),
~element("*", [])),
~element("div", [exists("flag")]));
assert !root.matches_selector(~copy sel2);
assert !child1.matches_selector(~copy sel2);
assert !child2.matches_selector(~copy sel2);
assert gchild.matches_selector(~copy sel2);
assert ggchild.matches_selector(~copy sel2);
assert gggchild.matches_selector(~sel2);
let sel3 = sibling(~element("*", []), ~element("*", []));
assert !root.matches_selector(~copy sel3);
assert child1.matches_selector(~copy sel3);
assert child2.matches_selector(~copy sel3);
assert !gchild.matches_selector(~copy sel3);
assert !ggchild.matches_selector(~copy sel3);
assert !gggchild.matches_selector(~sel3);
let sel4 = descendant(~child(~element("*", [exists("class")]),
~element("*", [])),
~element("*", []));
assert !root.matches_selector(~copy sel4);
assert !child1.matches_selector(~copy sel4);
assert !child2.matches_selector(~copy sel4);
assert gchild.matches_selector(~copy sel4);
assert ggchild.matches_selector(~copy sel4);
assert gggchild.matches_selector(~sel4);
}
}

View file

@ -1,35 +1,39 @@
#[doc="High-level interface to CSS selector matching."]
import dom::style::{display_type, di_block, di_inline, di_none,
stylesheet};
import dom::base::{element, es_div, es_head, es_img, nk_element, nk_text};
import dom::base::{node};
import dom::base::node_kind;
import dom::rcu::reader_methods;
import /*layout::*/base::*; // FIXME: resolve bug requires *
import matching::matching_methods;
enum computed_style = {
mut display: display
};
enum display {
di_block,
di_inline,
di_none
}
type computed_style = {mut display : display_type,
mut back_color : uint};
#[doc="Returns the default style for the given node kind."]
fn default_style_for_node_kind(kind: node_kind) -> computed_style {
alt kind {
nk_text(*) {
computed_style({ mut display: di_inline })
}
nk_element(element) {
alt *element.subclass {
es_div { computed_style({ mut display: di_block }) }
es_head { computed_style({ mut display: di_none }) }
es_img(*) { computed_style({ mut display: di_inline }) }
es_unknown { computed_style({ mut display: di_inline }) }
}
nk_text(*) {
{mut display: di_inline,
mut back_color : 256u*256u*256u-1u}
}
nk_element(element) {
let r = rand::rng();
let rand_color = 256u*256u*((r.next() & (255 as u32)) as uint)
+ 256u*((r.next() & (255 as u32)) as uint)
+ ((r.next() & (255 as u32)) as uint);
alt *element.subclass {
es_div { {mut display : di_block,
mut back_color : rand_color} }
es_head { {mut display : di_none, mut back_color : rand_color} }
es_img(*) { {mut display : di_inline, mut back_color : rand_color} }
es_unknown { {mut display : di_inline, mut back_color :
rand_color} }
}
}
}
}
@ -41,14 +45,13 @@ impl style_priv for node {
the node (the reader-auxiliary box in the RCU model) and populates it
with the computed style.
"]
fn recompute_style() {
let default_style: computed_style =
default_style_for_node_kind(self.rd { |n| copy *n.kind });
fn recompute_style(styles : stylesheet) {
let style = self.match_css_style(styles);
#debug("recomputing style; parent node:");
let the_layout_data = @layout_data({
mut computed_style: default_style,
mut computed_style: style,
mut box: none
});
@ -78,12 +81,14 @@ impl style_methods for node {
This is, importantly, the function that creates the layout data for
the node (the reader-auxiliary box in the RCU model) and populates it
with the computed style.
TODO: compute the style of multiple nodes in parallel.
"]
fn recompute_style_for_subtree() {
self.recompute_style();
fn recompute_style_for_subtree(styles : stylesheet) {
self.recompute_style(styles);
for ntree.each_child(self) {
|kid|
kid.recompute_style_for_subtree();
kid.recompute_style_for_subtree(styles);
}
}
}

View file

@ -21,6 +21,7 @@ impl methods for token_reader {
}
fn unget(tok : token) {
assert self.lookahead.is_none();
self.lookahead = some(tok);
}
}
@ -85,7 +86,7 @@ fn parse_color(color : str) -> uint {
i += 1u;
while i < color_vec.len() && color_vec[i] != ',' as u8 {
while i < color_vec.len() && color_vec[i] != ')' as u8 {
blue_vec += [color_vec[i]];
i += 1u;
}
@ -220,8 +221,9 @@ fn parse_rule(reader : token_reader) -> option<~rule> {
}
"display" {
alt val {
"inline" { desc_list += [display(inline)]; }
"block" { desc_list += [display(block)]; }
"inline" { desc_list += [display(di_inline)]; }
"block" { desc_list += [display(di_block)]; }
"none" { desc_list += [display(di_none)]; }
_ { #debug["Recieved unknown display value '%s'",
val]; }
}

View file

@ -493,7 +493,7 @@ fn spawn_html_parser_task(filename: str) -> port<html::token> {
ret result_port;
}
fn spawn_css_parser_task(filename: str) -> port<css::token> {
fn spawn_css_lexer_task(filename: str) -> port<css::token> {
let result_port = port();
let result_chan = chan(result_port);
task::spawn {||

View file

@ -37,6 +37,7 @@ mod layout {
mod style {
mod apply;
mod style;
mod matching;
}
mod base;

View file

@ -3,3 +3,8 @@ 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 }
* {background-color : red}
* * * * {background-color : black}
div div {background-color : green}
* * div {background-color : blue}
div div div {background-color : rgb(100,100,100)}

View file

@ -0,0 +1,16 @@
.green {background-color : green}
.blue {background-color : blue}
.red {background-color : red}
.white {background-color : white}
.black {background-color : black}
.brown {background-color : rgb(200,100,0)}
.gray {background-color : rgb(100,100,100)}
.lightgray {background-color : rgb(200,200,200)}
.darkgray {background-color : rgb(50,50,50)}
.cyan {background-color : rgb(0,255,255)}
.maroon {background-color : rgb(100,0,20)}
.pink {background-color : rgb(255,0,255)}
.orange {background-color : rgb(255,175,0)}
.violet {background-color : rgb(100,0,150)}
.darkgreen {background-color : rgb(0,100,0)}
.darkblue {background-color : rgb(0,0,100)}

View file

@ -0,0 +1,12 @@
<div class="darkgrey">
<div class="green">
<div class="darkblue"><img class="maroon"></img><div class="darkgreen"><img class="gray"></img></div></div>
<img class="black"></img>
<img class="brown"></img>
<div class="pink"><img class="orange"></img><img class="violet"></img></div>
<div class="lightgray">
<img class="blue"></img>
<img class="red"></img>
</div>
</div>
</div>