servo: Added width and height parsing

Source-Repo: https://github.com/servo/servo
Source-Revision: ed99449f52f5d927893408448a56f9c247f8e098
This commit is contained in:
Margaret Meyerhofer 2012-06-28 16:01:36 -07:00
Родитель d6080bd836
Коммит 97da506abd
14 изменённых файлов: 550 добавлений и 362 удалений

Просмотреть файл

@ -20,7 +20,6 @@ import dom::style;
import dom::style::Stylesheet; import dom::style::Stylesheet;
import gfx::renderer::Sink; import gfx::renderer::Sink;
import parser::html_lexer::spawn_html_lexer_task; import parser::html_lexer::spawn_html_lexer_task;
import parser::css_builder::build_stylesheet;
import parser::html_builder::build_dom; import parser::html_builder::build_dom;
import layout::layout_task; import layout::layout_task;
import layout_task::{Layout, BuildMsg}; import layout_task::{Layout, BuildMsg};

Просмотреть файл

@ -1,16 +1,37 @@
import util::color::Color; import util::color::Color;
enum DisplayType{ #[doc = "
Defines how css rules, both selectors and style specifications, are
stored. CSS selector-matching rules, as presented by
http://www.w3.org/TR/CSS2/selector.html are represented by nested, structural types,
"]
enum DisplayType {
DisBlock, DisBlock,
DisInline, DisInline,
DisNone DisNone
} }
enum StyleDeclaration{ enum Unit {
FontSize(uint), // Currently assumes format '# pt' Auto,
Percent(float),
In(float),
Mm(float),
Cm(float),
Em(float),
Ex(float),
Pt(float),
Pc(float),
Px(float)
}
enum StyleDeclaration {
BackgroundColor(Color),
Display(DisplayType), Display(DisplayType),
FontSize(Unit),
Height(Unit),
TextColor(Color), TextColor(Color),
BackgroundColor(Color) Width(Unit)
} }
enum Attr{ enum Attr{

Просмотреть файл

@ -13,9 +13,9 @@ import image::base::image;
import layout::block::block_layout_methods; import layout::block::block_layout_methods;
import layout::inline::inline_layout_methods; import layout::inline::inline_layout_methods;
import util::tree; import util::tree;
import util::color::Color; import util::color::{Color, css_colors};
import text::text_box; import text::text_box;
import style::style::computed_style; import style::style::SpecifiedStyle;
import text::text_layout_methods; import text::text_layout_methods;
import vec::{push, push_all}; import vec::{push, push_all};
@ -28,11 +28,11 @@ enum BoxKind {
class Appearance { class Appearance {
let mut background_image: option<@image>; let mut background_image: option<@image>;
let mut background_color: option<Color>; let mut background_color: Color;
new() { new() {
self.background_image = none; self.background_image = none;
self.background_color = none; self.background_color = css_colors::black();
} }
} }
@ -53,7 +53,7 @@ class Box {
} }
enum LayoutData = { enum LayoutData = {
mut computed_style: ~computed_style, mut specified_style: ~SpecifiedStyle,
mut box: option<@Box> mut box: option<@Box>
}; };

Просмотреть файл

@ -39,9 +39,8 @@ fn create_context(parent_node: Node, parent_box: @Box) -> ctxt {
impl methods for ctxt { impl methods for ctxt {
#[doc=" #[doc="
Constructs boxes for the parent's children, when the parent's 'display' Constructs boxes for the parent's children, when the parent's 'display' attribute is 'block'.
attribute is 'block'. "]
"]
fn construct_boxes_for_block_children() { fn construct_boxes_for_block_children() {
for NTree.each_child(self.parent_node) |kid| { for NTree.each_child(self.parent_node) |kid| {
@ -49,46 +48,48 @@ impl methods for ctxt {
let kid_box = kid.construct_boxes(); let kid_box = kid.construct_boxes();
// Determine the child's display. // Determine the child's display.
let disp = kid.get_computed_style().display; let disp = kid.get_specified_style().display_type;
if disp != DisInline { if disp != some(DisInline) {
self.finish_anonymous_box_if_necessary(); self.finish_anonymous_box_if_necessary();
} }
// Add the child's box to the current enclosing box or the current anonymous box. // Add the child's box to the current enclosing box or the current anonymous box.
alt kid.get_computed_style().display { alt kid.get_specified_style().display_type {
DisBlock { some(DisBlock) {
BTree.add_child(self.parent_box, kid_box); BTree.add_child(self.parent_box, kid_box);
} }
DisInline { some(DisInline) {
let anon_box = alt self.anon_box { let anon_box = alt self.anon_box {
none { none {
// //
// The anonymous box inherits the attributes of its parents for now, so // The anonymous box inherits the attributes of its parents for now, so
// that properties of intrinsic boxes are not spread to their parenting // that properties of intrinsic boxes are not spread to their parenting
// anonymous box. // anonymous box.
// //
// TODO: check what CSS actually specifies // TODO: check what CSS actually specifies
// //
let b = @Box(self.parent_node, InlineBox); let b = @Box(self.parent_node, InlineBox);
self.anon_box = some(b); self.anon_box = some(b);
b b
} }
some(b) { b } some(b) { b }
}; };
BTree.add_child(anon_box, kid_box); BTree.add_child(anon_box, kid_box);
} }
DisNone { some(DisNone) {
// Nothing to do. // Nothing to do.
} }
_ { //hack for now
}
} }
} }
} }
#[doc=" #[doc="
Constructs boxes for the parent's children, when the parent's 'display' Constructs boxes for the parent's children, when the parent's 'display'
attribute is 'inline'. attribute is 'inline'.
"] "]
fn construct_boxes_for_inline_children() { fn construct_boxes_for_inline_children() {
for NTree.each_child(self.parent_node) |kid| { for NTree.each_child(self.parent_node) |kid| {
@ -96,24 +97,26 @@ impl methods for ctxt {
let kid_box = kid.construct_boxes(); let kid_box = kid.construct_boxes();
// Determine the child's display. // Determine the child's display.
let disp = kid.get_computed_style().display; let disp = kid.get_specified_style().display_type;
if disp != DisInline { if disp != some(DisInline) {
// TODO // TODO
} }
// Add the child's box to the current enclosing box. // Add the child's box to the current enclosing box.
alt kid.get_computed_style().display { alt kid.get_specified_style().display_type {
DisBlock { some(DisBlock) {
// TODO // TODO
#warn("TODO: non-inline display found inside inline box"); #warn("TODO: non-inline display found inside inline box");
BTree.add_child(self.parent_box, kid_box); BTree.add_child(self.parent_box, kid_box);
} }
DisInline { some(DisInline) {
BTree.add_child(self.parent_box, kid_box); BTree.add_child(self.parent_box, kid_box);
} }
DisNone { some(DisNone) {
// Nothing to do. // Nothing to do.
} }
_ { //hack for now
}
} }
} }
} }
@ -123,10 +126,12 @@ impl methods for ctxt {
#debug("parent node:"); #debug("parent node:");
self.parent_node.dump(); self.parent_node.dump();
alt self.parent_node.get_computed_style().display { alt self.parent_node.get_specified_style().display_type {
DisBlock { self.construct_boxes_for_block_children(); } some(DisBlock) { self.construct_boxes_for_block_children(); }
DisInline { self.construct_boxes_for_inline_children(); } some(DisInline) { self.construct_boxes_for_inline_children(); }
DisNone { /* Nothing to do. */ } some(DisNone) { /* Nothing to do. */ }
_ { //hack for now
}
} }
self.finish_anonymous_box_if_necessary(); self.finish_anonymous_box_if_necessary();
@ -134,13 +139,13 @@ impl methods for ctxt {
} }
#[doc=" #[doc="
Flushes the anonymous box we're creating if it exists. This appends the Flushes the anonymous box we're creating if it exists. This appends the
anonymous box to the block. anonymous box to the block.
"] "]
fn finish_anonymous_box_if_necessary() { fn finish_anonymous_box_if_necessary() {
alt copy self.anon_box { alt copy self.anon_box {
none { /* Nothing to do. */ } none { /* Nothing to do. */ }
some(b) { BTree.add_child(self.parent_box, b); } some(b) { BTree.add_child(self.parent_box, b); }
} }
self.anon_box = none; self.anon_box = none;
} }
@ -152,21 +157,21 @@ trait box_builder_priv {
impl box_builder_priv of box_builder_priv for Node { impl box_builder_priv of box_builder_priv for Node {
#[doc=" #[doc="
Determines the kind of box that this node needs. Also, for images, computes the intrinsic Determines the kind of box that this node needs. Also, for images, computes the intrinsic
size. size.
"] "]
fn determine_box_kind() -> BoxKind { fn determine_box_kind() -> BoxKind {
alt self.read(|n| copy n.kind) { alt self.read(|n| copy n.kind) {
~Text(string) { ~Text(string) {
TextBox(@text_box(copy string)) TextBox(@text_box(copy string))
} }
~Element(element) { ~Element(element) {
alt *element.kind { alt *element.kind {
HTMLDivElement { BlockBox } HTMLDivElement { BlockBox }
HTMLImageElement({size}) { IntrinsicBox(@size) } HTMLImageElement({size}) { IntrinsicBox(@size) }
UnknownElement { InlineBox } UnknownElement { InlineBox }
}
} }
}
} }
} }
} }
@ -181,13 +186,13 @@ impl box_builder_methods of box_builder_methods for Node {
let box_kind = self.determine_box_kind(); let box_kind = self.determine_box_kind();
let my_box = @Box(self, box_kind); let my_box = @Box(self, box_kind);
alt box_kind { alt box_kind {
BlockBox | InlineBox { BlockBox | InlineBox {
let cx = create_context(self, my_box); let cx = create_context(self, my_box);
cx.construct_boxes_for_children(); cx.construct_boxes_for_children();
} }
_ { _ {
// Nothing to do. // Nothing to do.
} }
} }
ret my_box; ret my_box;
} }

Просмотреть файл

@ -70,9 +70,10 @@ fn box_to_display_items(box: @Box, origin: Point2D<au>) -> ~[dl::display_item] {
#debug("request to display a box from origin %?", origin); #debug("request to display a box from origin %?", origin);
let bounds = Rect(origin, copy box.bounds.size); let bounds = Rect(origin, copy box.bounds.size);
let col = box.appearance.background_color;
alt (box.kind, box.appearance.background_image, box.appearance.background_color) { alt (box.kind, box.appearance.background_image) {
(TextBox(subbox), _, _) { (TextBox(subbox), _) {
let run = copy subbox.run; let run = copy subbox.run;
assert run.is_some(); assert run.is_some();
push(items, dl::display_item({ push(items, dl::display_item({
@ -84,28 +85,20 @@ fn box_to_display_items(box: @Box, origin: Point2D<au>) -> ~[dl::display_item] {
bounds: bounds bounds: bounds
})); }));
} }
(_, some(image), some(*)) | (_, some(image), none) { (_, some(image)) {
push(items, dl::display_item({ push(items, dl::display_item({
item_type: dl::display_item_image(~copy *image), item_type: dl::display_item_image(~copy *image),
bounds: bounds bounds: bounds
})); }));
} }
(_, none, some(col)) { (_, none) {
#debug("Assigning color %? to box with bounds %?", col, bounds); #debug("Assigning color %? to box with bounds %?", col, bounds);
let col = box.appearance.background_color;
push(items, dl::display_item({ push(items, dl::display_item({
item_type: dl::display_item_solid_color(col.red, col.green, col.blue), item_type: dl::display_item_solid_color(col.red, col.green, col.blue),
bounds: bounds bounds: bounds
})); }));
} }
(_, none, none) {
let r = rand::rng();
push(items, dl::display_item({
item_type: dl::display_item_solid_color(r.next() as u8,
r.next() as u8,
r.next() as u8),
bounds: bounds
}));
}
} }
#debug("layout: display items: %?", items); #debug("layout: display items: %?", items);

Просмотреть файл

@ -3,8 +3,8 @@
import dom::base::{Element, HTMLImageElement, Node}; import dom::base::{Element, HTMLImageElement, Node};
import dom::rcu::ReaderMethods; import dom::rcu::ReaderMethods;
import image::base::load; import image::base::load;
import base::{Box, BTree, NTree, LayoutData, BoxTreeReadMethods}; import base::{Box, BTree, NTree, LayoutData, BoxTreeReadMethods, SpecifiedStyle};
import style::style_methods; import style::{default_style_methods, style_methods};
trait ApplyStyleBoxMethods { trait ApplyStyleBoxMethods {
fn apply_style_for_subtree(); fn apply_style_for_subtree();
@ -19,15 +19,24 @@ impl ApplyStyleBoxMethods of ApplyStyleBoxMethods for @Box {
} }
} }
#[doc="Applies CSS style."] #[doc="Applies CSS style to a layout box.
Get the specified style and apply the existing traits to a
layout box. If a trait does not exist, calculate the default
value for the given type of element and use that instead.
"]
fn apply_style() { fn apply_style() {
// Right now, we only handle images. // Right now, we only handle images.
self.node.read(|node| { self.node.read(|node| {
alt node.kind { alt node.kind {
~Element(element) { ~Element(element) {
let style = self.node.get_computed_style(); let style = self.node.get_specified_style();
self.appearance.background_color = some(style.back_color); self.appearance.background_color = alt style.background_color {
some(col) { col }
none { node.kind.default_color() }
};
alt element.kind { alt element.kind {
~HTMLImageElement(*) { ~HTMLImageElement(*) {

Просмотреть файл

@ -5,9 +5,9 @@ import dom::base;
import base::{ElementData, Node, Text}; import base::{ElementData, Node, Text};
import dom::style::{Selector, StyleDeclaration, FontSize, Display, TextColor, BackgroundColor, import dom::style::{Selector, StyleDeclaration, FontSize, Display, TextColor, BackgroundColor,
Stylesheet, Element, Child, Descendant, Sibling, Attr, Exact, Exists, Includes, Stylesheet, Element, Child, Descendant, Sibling, Attr, Exact, Exists, Includes,
StartsWith}; StartsWith, Width, Height};
import dom::rcu::ReaderMethods; import dom::rcu::ReaderMethods;
import style::{computed_style, default_style_for_node_kind}; import style::{SpecifiedStyle};
export matching_methods; export matching_methods;
@ -70,20 +70,20 @@ impl priv_matching_methods of priv_matching_methods for Node {
Child(_, _) | Descendant(_, _) | Sibling(_, _) { ret false; } Child(_, _) | Descendant(_, _) | Sibling(_, _) { ret false; }
Element(tag, attrs) { Element(tag, attrs) {
alt self.read(|n| copy *n.kind) { alt self.read(|n| copy *n.kind) {
base::Element(elmt) { base::Element(elmt) {
if !(tag == ~"*" || tag == elmt.tag_name) { if !(tag == ~"*" || tag == elmt.tag_name) {
ret false; ret false;
}
let mut i = 0u;
while i < attrs.len() {
if !attrs_match(attrs[i], elmt) { ret false; }
i += 1u;
}
ret true;
} }
Text(str) { /*fall through, currently unsupported*/ }
let mut i = 0u;
while i < attrs.len() {
if !attrs_match(attrs[i], elmt) { ret false; }
i += 1u;
}
ret true;
}
Text(str) { /*fall through, currently unsupported*/ }
} }
} }
} }
@ -113,16 +113,16 @@ impl priv_matching_methods of priv_matching_methods for Node {
//loop over all ancestors to check if they are the person //loop over all ancestors to check if they are the person
//we should be descended from. //we should be descended from.
let mut cur_parent = alt self.read(|n| n.tree.parent) { let mut cur_parent = alt self.read(|n| n.tree.parent) {
some(parent) { parent } some(parent) { parent }
none { ret false; } none { ret false; }
}; };
loop { loop {
if cur_parent.matches_selector(sel1) { ret true; } if cur_parent.matches_selector(sel1) { ret true; }
cur_parent = alt cur_parent.read(|n| n.tree.parent) { cur_parent = alt cur_parent.read(|n| n.tree.parent) {
some(parent) { parent } some(parent) { parent }
none { ret false; } none { ret false; }
}; };
} }
} }
@ -131,18 +131,18 @@ impl priv_matching_methods of priv_matching_methods for Node {
// Loop over this node's previous siblings to see if they match. // Loop over this node's previous siblings to see if they match.
alt self.read(|n| n.tree.prev_sibling) { alt self.read(|n| n.tree.prev_sibling) {
some(sib) { some(sib) {
let mut cur_sib = sib; let mut cur_sib = sib;
loop { loop {
if cur_sib.matches_selector(sel1) { ret true; } if cur_sib.matches_selector(sel1) { ret true; }
cur_sib = alt cur_sib.read(|n| n.tree.prev_sibling) { cur_sib = alt cur_sib.read(|n| n.tree.prev_sibling) {
some(sib) { sib } some(sib) { sib }
none { break; } none { break; }
}; };
}
} }
none { } }
none { }
} }
// check the rest of the siblings // check the rest of the siblings
@ -176,9 +176,12 @@ impl priv_style_methods of priv_style_methods for Node {
fn update_style(decl : StyleDeclaration) { fn update_style(decl : StyleDeclaration) {
self.aux(|layout| { self.aux(|layout| {
alt decl { alt decl {
Display(dis) { layout.computed_style.display = dis; } BackgroundColor(col) { layout.specified_style.background_color = some(col); }
BackgroundColor(col) { layout.computed_style.back_color = col; } Display(dis) { layout.specified_style.display_type = some(dis); }
TextColor(*) | FontSize(*) { /* not supported yet */ } FontSize(size) { layout.specified_style.font_size = some(size); }
Height(size) { layout.specified_style.height = some(size); }
TextColor(col) { layout.specified_style.text_color = some(col); }
Width(size) { layout.specified_style.width = some(size); }
} }
}) })
} }
@ -208,7 +211,7 @@ impl matching_methods of matching_methods for Node {
} }
} }
self.aux(|a| #debug["Changed the style to: %?", copy *a.computed_style]); self.aux(|a| #debug["Changed the style to: %?", copy *a.specified_style]);
} }
} }
@ -308,8 +311,7 @@ mod test {
scope.add_child(gchild, ggchild); scope.add_child(gchild, ggchild);
scope.add_child(ggchild, gggchild); scope.add_child(ggchild, gggchild);
let sel1 = Descendant(~Element(~"*", ~[Exact(~"class", ~"blue")]), let sel1 = Descendant(~Element(~"*", ~[Exact(~"class", ~"blue")]), ~Element(~"*", ~[]));
~Element(~"*", ~[]));
assert !root.matches_selector(~copy sel1); assert !root.matches_selector(~copy sel1);
assert child1.matches_selector(~copy sel1); assert child1.matches_selector(~copy sel1);
@ -338,8 +340,7 @@ mod test {
assert !ggchild.matches_selector(~copy sel3); assert !ggchild.matches_selector(~copy sel3);
assert !gggchild.matches_selector(~sel3); assert !gggchild.matches_selector(~sel3);
let sel4 = Descendant(~Child(~Element(~"*", ~[Exists(~"class")]), let sel4 = Descendant(~Child(~Element(~"*", ~[Exists(~"class")]), ~Element(~"*", ~[])),
~Element(~"*", ~[])),
~Element(~"*", ~[])); ~Element(~"*", ~[]));
assert !root.matches_selector(~copy sel4); assert !root.matches_selector(~copy sel4);

Просмотреть файл

@ -2,7 +2,7 @@
import arc::{arc, get, clone}; import arc::{arc, get, clone};
import dom::style::{DisplayType, DisBlock, DisInline, DisNone, Stylesheet}; import dom::style::{DisplayType, DisBlock, DisInline, DisNone, Stylesheet, Unit};
import dom::base::{Element, HTMLDivElement, HTMLHeadElement, HTMLImageElement, Node, NodeKind}; import dom::base::{Element, HTMLDivElement, HTMLHeadElement, HTMLImageElement, Node, NodeKind};
import dom::base::{Text}; import dom::base::{Text};
import dom::rcu::ReaderMethods; import dom::rcu::ReaderMethods;
@ -11,26 +11,63 @@ import util::color::{Color, rgb};
import util::color::css_colors::{white, black}; import util::color::css_colors::{white, black};
import base::{LayoutData, NTree, NodeTreeReadMethods}; import base::{LayoutData, NTree, NodeTreeReadMethods};
type computed_style = {mut display : DisplayType, mut back_color : Color}; type SpecifiedStyle = {mut background_color : option<Color>,
mut display_type : option<DisplayType>,
mut font_size : option<Unit>,
mut height : option<Unit>,
mut text_color : option<Color>,
mut width : option<Unit>
};
#[doc="Returns the default style for the given node kind."] trait default_style_methods {
fn default_style_for_node_kind(kind: NodeKind) -> computed_style { fn default_color() -> Color;
alt kind { fn default_display_type() -> DisplayType;
Text(*) { }
{mut display: DisInline, mut back_color: white()}
}
Element(element) {
let r = rand::rng();
let rand_color = rgb(r.next() as u8, r.next() as u8, r.next() as u8);
alt *element.kind { #[doc="Default stylesfor various attributes in case they don't get initialized from css selectors"]
HTMLDivElement { {mut display: DisBlock, mut back_color: rand_color} } impl default_style_methods of default_style_methods for NodeKind {
HTMLHeadElement { {mut display: DisNone, mut back_color: rand_color} } fn default_color() -> Color {
HTMLImageElement(*) { {mut display: DisInline, mut back_color: rand_color} } alt self {
UnknownElement { {mut display: DisInline, mut back_color: rand_color} } Text(*) { white() }
Element(*) {
let r = rand::rng();
rgb(r.next() as u8, r.next() as u8, r.next() as u8)
}
} }
}
} }
fn default_display_type() -> DisplayType {
alt self {
Text(*) { DisInline }
Element(element) {
alt *element.kind {
HTMLDivElement { DisBlock }
HTMLHeadElement { DisNone }
HTMLImageElement(*) { DisInline }
UnknownElement { DisInline }
}
}
}
}
}
#[doc="Create a specified style that can be used to initialize a node before selector matching.
Everything is initialized to none except the display style. The
default value of thee display style is computed so that it can be
used to short-circuit selector matching to avoid computing style
for children of display:none objects.
"]
fn empty_style_for_node_kind(kind: NodeKind) -> SpecifiedStyle {
let display_type = kind.default_display_type();
{mut background_color : none,
mut display_type : some(display_type),
mut font_size : none,
mut height : none,
mut text_color : none,
mut width : none}
} }
trait style_priv { trait style_priv {
@ -40,13 +77,17 @@ trait style_priv {
impl style_priv of style_priv for Node { impl style_priv of style_priv for Node {
#[doc="Set a default auxilliary data so that other threads can modify it. #[doc="Set a default auxilliary data so that other threads can modify it.
This is, importantly, the function that creates the layout data for the node (the reader- This is, importantly, the function that creates the layout
auxiliary box in the RCU model) and populates it with the default style. data for the node (the reader-auxiliary box in the RCU model)
and populates it with the default style.
"] "]
// TODO: we should look into folding this into building the dom,
// instead of doing a linear sweep afterwards.
fn initialize_style() { fn initialize_style() {
let node_kind = self.read(|n| copy *n.kind); let node_kind = self.read(|n| copy *n.kind);
let the_layout_data = @LayoutData({ let the_layout_data = @LayoutData({
mut computed_style : ~default_style_for_node_kind(node_kind), mut specified_style : ~empty_style_for_node_kind(node_kind),
mut box : none mut box : none
}); });
@ -56,7 +97,7 @@ impl style_priv of style_priv for Node {
trait style_methods { trait style_methods {
fn initialize_style_for_subtree(); fn initialize_style_for_subtree();
fn get_computed_style() -> computed_style; fn get_specified_style() -> SpecifiedStyle;
fn recompute_style_for_subtree(styles : arc<Stylesheet>); fn recompute_style_for_subtree(styles : arc<Stylesheet>);
} }
@ -76,11 +117,11 @@ impl style_methods of style_methods for Node {
TODO: Return a safe reference; don't copy. TODO: Return a safe reference; don't copy.
"] "]
fn get_computed_style() -> computed_style { fn get_specified_style() -> SpecifiedStyle {
if !self.has_aux() { if !self.has_aux() {
fail ~"get_computed_style() called on a node without a style!"; fail ~"get_computed_style() called on a node without a style!";
} }
ret copy *self.aux(|x| copy x).computed_style; ret copy *self.aux(|x| copy x).specified_style;
} }
#[doc=" #[doc="

Просмотреть файл

@ -1,27 +1,30 @@
#[doc="Constructs a list of style rules from a token stream"] #[doc="Constructs a list of css style rules from a token stream"]
// TODO: fail according to the css spec instead of failing when things // TODO: fail according to the css spec instead of failing when things
// are not as expected // are not as expected
import dom::style; import dom::style;
import style::{DisInline, DisBlock, DisNone, Display, TextColor, BackgroundColor, FontSize}; import style::{DisInline, DisBlock, DisNone, Display, TextColor, BackgroundColor, FontSize,
Height, Width, StyleDeclaration, Selector};
import parser::css_lexer::{Token, StartDescription, EndDescription, import parser::css_lexer::{Token, StartDescription, EndDescription,
Descendant, Child, Sibling, Descendant, Child, Sibling,
Comma, Element, Attr, Description, Comma, Element, Attr, Description,
Eof}; Eof};
import comm::recv; import comm::recv;
import option::is_none; import option::{map, is_none};
import vec::push;
import parser::parser_util::{parse_display_type, parse_font_size, parse_size};
import util::color::parsing::parse_color; import util::color::parsing::parse_color;
import vec::push; import vec::push;
type TokenReader = {stream : port<Token>, mut lookahead : option<Token>}; type TokenReader = {stream : port<Token>, mut lookahead : option<Token>};
trait methods { trait util_methods {
fn get() -> Token; fn get() -> Token;
fn unget(-tok : Token); fn unget(-tok : Token);
} }
impl methods of methods for TokenReader { impl util_methods of util_methods for TokenReader {
fn get() -> Token { fn get() -> Token {
alt copy self.lookahead { alt copy self.lookahead {
some(tok) { self.lookahead = none; copy tok } some(tok) { self.lookahead = none; copy tok }
@ -35,153 +38,160 @@ impl methods of methods for TokenReader {
} }
} }
fn parse_element(reader : TokenReader) -> option<~style::Selector> { trait parser_methods {
// Get the current element type fn parse_element() -> option<~style::Selector>;
let elmt_name = alt reader.get() { fn parse_selector() -> option<~[~Selector]>;
Element(tag) { copy tag } fn parse_description() -> option<~[StyleDeclaration]>;
Eof { ret none; } fn parse_rule() -> option<~style::Rule>;
_ { fail ~"Expected an element" }
};
let mut attr_list = ~[];
// Get the attributes associated with that element
loop {
let tok = reader.get();
alt tok {
Attr(attr) { push(attr_list, copy attr); }
StartDescription | Descendant | Child | Sibling | Comma {
reader.unget(tok);
break;
}
Eof { ret none; }
Element(_) { fail ~"Unexpected second element without "
+ ~"relation to first element"; }
EndDescription { fail ~"Unexpected '}'"; }
Description(_, _) { fail ~"Unexpected description"; }
}
}
ret some(~style::Element(elmt_name, attr_list));
} }
fn parse_rule(reader : TokenReader) -> option<~style::Rule> { impl parser_methods of parser_methods for TokenReader {
let mut sel_list = ~[]; fn parse_element() -> option<~style::Selector> {
let mut desc_list = ~[]; // Get the current element type
let elmt_name = alt self.get() {
Element(tag) { copy tag }
Eof { ret none; }
_ { fail ~"Expected an element" }
};
// Collect all the selectors that this rule applies to let mut attr_list = ~[];
loop {
let mut cur_sel;
alt parse_element(reader) { // Get the attributes associated with that element
some(elmt) { cur_sel = copy elmt; } loop {
none { ret none; } // we hit an eof in the middle of a rule let tok = self.get();
} alt tok {
Attr(attr) { push(attr_list, copy attr); }
StartDescription | Descendant | Child | Sibling | Comma {
self.unget(tok);
break;
}
Eof { ret none; }
Element(_) { fail ~"Unexpected second element without relation to first element"; }
EndDescription { fail ~"Unexpected '}'"; }
Description(_, _) { fail ~"Unexpected description"; }
}
}
ret some(~style::Element(elmt_name, attr_list));
}
fn parse_selector() -> option<~[~Selector]> {
let mut sel_list = ~[];
// Collect all the selectors that this rule applies to
loop { loop {
let tok = reader.get(); let mut cur_sel;
alt tok {
Descendant { alt self.parse_element() {
alt parse_element(reader) { some(elmt) { cur_sel = copy elmt; }
some(elmt) { none { ret none; } // we hit an eof in the middle of a rule
let built_sel <- cur_sel; }
let new_sel = copy elmt;
cur_sel <- ~style::Descendant(built_sel, new_sel) loop {
} let tok = self.get();
none { ret none; }
}
}
Child {
alt parse_element(reader) {
some(elmt) {
let built_sel <- cur_sel;
let new_sel = copy elmt;
cur_sel <- ~style::Child(built_sel, new_sel)
}
none { ret none; }
}
}
Sibling {
alt parse_element(reader) {
some(elmt) {
let built_sel <- cur_sel;
let new_sel = copy elmt;
cur_sel <- ~style::Sibling(built_sel, new_sel)
}
none { ret none; }
}
}
StartDescription {
let built_sel <- cur_sel;
push(sel_list, built_sel);
reader.unget(StartDescription);
break;
}
Comma {
let built_sel <- cur_sel; let built_sel <- cur_sel;
push(sel_list, built_sel);
reader.unget(Comma);
break;
}
Attr(_) | EndDescription | Element(_) | Description(_, _) {
fail #fmt["Unexpected token %? in elements", tok];
}
Eof { ret none; }
}
}
// check if we should break out of the nesting loop as well
let tok = reader.get();
alt tok {
StartDescription { break; }
Comma { }
_ { reader.unget(tok); }
}
}
// Get the description to be applied to the selector
loop {
let tok = reader.get();
alt tok {
EndDescription { break; }
Description(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) { alt tok {
some(n) { push(desc_list, FontSize(n)); } Descendant {
none { fail ~"Nonnumber provided as font size"; } alt self.parse_element() {
some(elmt) {
let new_sel = copy elmt;
cur_sel <- ~style::Descendant(built_sel, new_sel)
}
none { ret none; }
}
}
Child {
alt self.parse_element() {
some(elmt) {
let new_sel = copy elmt;
cur_sel <- ~style::Child(built_sel, new_sel)
}
none { ret none; }
}
}
Sibling {
alt self.parse_element() {
some(elmt) {
let new_sel = copy elmt;
cur_sel <- ~style::Sibling(built_sel, new_sel)
}
none { ret none; }
}
}
StartDescription {
push(sel_list, built_sel);
self.unget(StartDescription);
break;
}
Comma {
push(sel_list, built_sel);
self.unget(Comma);
break;
}
Attr(_) | EndDescription | Element(_) | Description(_, _) {
fail #fmt["Unexpected token %? in elements", tok];
}
Eof { ret none; }
} }
}
~"display" {
alt val {
~"inline" { push(desc_list, Display(DisInline)); }
~"block" { push(desc_list, Display(DisBlock)); }
~"none" { push(desc_list, Display(DisNone)); }
_ { #debug["Recieved unknown display value '%s'", val]; }
}
}
~"color" {
push(desc_list, TextColor(parse_color(val)));
}
~"background-color" {
push(desc_list, BackgroundColor(parse_color(val)));
}
_ { #debug["Recieved unknown style property '%s'", val]; }
} }
}
Eof { ret none; } // check if we should break out of the nesting loop as well
StartDescription | Descendant | Child | Sibling // TODO: fix this when rust gets labelled loops
| Comma | Element(_) | Attr(_) { let tok = self.get();
fail #fmt["Unexpected token %? in description", tok]; alt tok {
} StartDescription { break; }
Comma { }
_ { self.unget(tok); }
}
} }
ret some(sel_list);
} }
ret some(~(sel_list, desc_list)); fn parse_description() -> option<~[StyleDeclaration]> {
let mut desc_list : ~[StyleDeclaration]= ~[];
// Get the description to be applied to the selector
loop {
let tok = self.get();
alt tok {
EndDescription { break; }
Description(prop, val) {
alt prop {
// TODO: have color parsing return an option instead of a real value
~"background-color" { push(desc_list, BackgroundColor(parse_color(val))); }
~"color" { push(desc_list, TextColor(parse_color(val))); }
~"display" { parse_display_type(val).map(|res| push(desc_list, Display(res))); }
~"font-size" { parse_font_size(val).map(|res| push(desc_list, FontSize(res))); }
~"height" { parse_size(val).map(|res| push(desc_list, Height(res))); }
~"width" { parse_size(val).map(|res| push(desc_list, Width(res))); }
_ { #debug["Recieved unknown style property '%s'", val]; }
}
}
Eof { ret none; }
StartDescription | Descendant | Child | Sibling | Comma | Element(_) | Attr(_) {
fail #fmt["Unexpected token %? in description", tok];
}
}
}
ret some(desc_list);
}
fn parse_rule() -> option<~style::Rule> {
let sel_list = alt self.parse_selector() {
some(list){ copy list }
none { ret none; }
};
// Get the description to be applied to the selector
let desc_list = alt self.parse_description() {
some(list) { copy list }
none { ret none; }
};
ret some(~(sel_list, desc_list));
}
} }
fn build_stylesheet(stream : port<Token>) -> ~[~style::Rule] { fn build_stylesheet(stream : port<Token>) -> ~[~style::Rule] {
@ -189,9 +199,9 @@ fn build_stylesheet(stream : port<Token>) -> ~[~style::Rule] {
let reader = {stream : stream, mut lookahead : none}; let reader = {stream : stream, mut lookahead : none};
loop { loop {
alt parse_rule(reader) { alt reader.parse_rule() {
some(rule) { push(rule_list, copy rule); } some(rule) { push(rule_list, copy rule); }
none { break; } none { break; }
} }
} }

Просмотреть файл

@ -1,3 +1,5 @@
#[doc = "Code to lex and tokenize css files."]
import comm::{port, chan}; import comm::{port, chan};
import dom::style; import dom::style;
import option::is_none; import option::is_none;
@ -48,9 +50,9 @@ impl css_methods of css_methods for CssLexer {
} }
let token = alt self.parser_state { let token = alt self.parser_state {
CssDescription { self.parse_css_description(ch) } CssDescription { self.parse_css_description(ch) }
CssAttribute { self.parse_css_attribute(ch) } CssAttribute { self.parse_css_attribute(ch) }
CssElement { self.parse_css_element(ch) } CssElement { self.parse_css_element(ch) }
CssRelation { self.parse_css_relation(ch) } CssRelation { self.parse_css_relation(ch) }
}; };
@ -221,12 +223,36 @@ impl css_methods of css_methods for CssLexer {
} }
fn parser(reader: io::reader, state : ParserState) -> CssLexer { fn parser(reader: io::reader, state : ParserState) -> CssLexer {
ret { input_state: {mut lookahead: none, reader: reader}, ret { input_state: {mut lookahead: none, reader: reader}, mut parser_state: state };
mut parser_state: state }; }
fn lex_css_from_bytes(-content : ~[u8], result_chan : chan<Token>) {
let reader = io::bytes_reader(content);
let lexer = parser(reader, CssElement);
loop {
let token = lexer.parse_css();
let should_break = (token == Eof);
result_chan.send(token);
if should_break {
break;
}
}
}
fn spawn_css_lexer_from_string(-content : ~str) -> port<Token> {
let result_port = port();
let result_chan = chan(result_port);
task::spawn(|| lex_css_from_bytes(str::bytes(content), result_chan));
ret result_port;
} }
#[warn(no_non_implicitly_copyable_typarams)] #[warn(no_non_implicitly_copyable_typarams)]
fn spawn_css_lexer_task(-filename: ~str) -> port<Token> { fn spawn_css_lexer_from_file(-filename: ~str) -> port<Token> {
let result_port = port(); let result_port = port();
let result_chan = chan(result_port); let result_chan = chan(result_port);
@ -235,22 +261,11 @@ fn spawn_css_lexer_task(-filename: ~str) -> port<Token> {
let file_try = io::read_whole_file(filename); let file_try = io::read_whole_file(filename);
// Check if the given css file existed, if it does, parse it, // Check if the given css file existed, if it does, parse it,
// otherwise just send an eof. This is a hack to allow // otherwise just send an eof.
// guessing that if foo.html exists, foo.css is the
// corresponding stylesheet.
if file_try.is_ok() { if file_try.is_ok() {
#debug["Lexing css sheet %s", copy filename]; #debug["Lexing css sheet %s", copy filename];
let file_data = file_try.get(); let file_data = file_try.get();
let reader = io::bytes_reader(file_data); lex_css_from_bytes(file_data, result_chan);
let lexer = parser(reader, CssElement);
loop {
let token = lexer.parse_css();
let should_break = token == Eof;
result_chan.send(token);
if should_break { break; }
}
} else { } else {
#debug["Failed to open css sheet %s", copy filename]; #debug["Failed to open css sheet %s", copy filename];
result_chan.send(Eof); result_chan.send(Eof);

Просмотреть файл

@ -11,11 +11,12 @@ import parser = parser::html_lexer;
import parser::Token; import parser::Token;
import dom::style::Stylesheet; import dom::style::Stylesheet;
import vec::{push, push_all_move, flat_map}; import vec::{push, push_all_move, flat_map};
import dvec::extensions; import dvec::extensions;
enum css_message { enum CSSMessage {
file(~str), File(~str),
exit Exit
} }
#[warn(no_non_implicitly_copyable_typarams)] #[warn(no_non_implicitly_copyable_typarams)]
@ -59,15 +60,15 @@ fn link_up_attribute(scope: NodeScope, node: Node, -key: ~str, -value: ~str) {
fn build_element_kind(tag_name: ~str) -> ~ElementKind { fn build_element_kind(tag_name: ~str) -> ~ElementKind {
alt tag_name { alt tag_name {
~"div" { ~HTMLDivElement } ~"div" { ~HTMLDivElement }
~"img" { ~"img" {
~HTMLImageElement({ ~HTMLImageElement({
mut size: Size2D(geometry::px_to_au(100), mut size: Size2D(geometry::px_to_au(100),
geometry::px_to_au(100)) geometry::px_to_au(100))
}) })
} }
~"head" { ~HTMLHeadElement } ~"head" { ~HTMLHeadElement }
_ { ~UnknownElement } _ { ~UnknownElement }
} }
} }
@ -85,25 +86,26 @@ spawned, collates them, and sends them to the given result channel.
* `from_parent` - A port on which to receive new links. * `from_parent` - A port on which to receive new links.
"] "]
fn css_link_listener(to_parent : chan<Stylesheet>, from_parent : port<css_message>) { fn css_link_listener(to_parent : chan<Stylesheet>, from_parent : port<CSSMessage>) {
let mut result_vec = ~[]; let mut result_vec = ~[];
loop { loop {
alt from_parent.recv() { alt from_parent.recv() {
file(filename) { File(filename) {
let result_port = comm::port(); let result_port = comm::port();
let result_chan = comm::chan(result_port); let result_chan = comm::chan(result_port);
let filename = copy filename; let filename = copy filename;
task::spawn(|| { task::spawn(|| {
//TODO: deal with extraneous copies //TODO: deal with extraneous copies
let filename <- copy filename; let filename <- copy filename;
let css_stream = css_lexer::spawn_css_lexer_task(filename); let css_stream = css_lexer::spawn_css_lexer_from_file(filename);
let mut css_rules = css_builder::build_stylesheet(css_stream); let mut css_rules = css_builder::build_stylesheet(css_stream);
result_chan.send(css_rules); result_chan.send(css_rules);
}); });
push(result_vec, result_port); push(result_vec, result_port);
} }
exit { Exit {
break; break;
} }
} }
@ -159,7 +161,7 @@ fn build_dom(scope: NodeScope, stream: port<Token>) -> (Node, port<Stylesheet>)
alt elmt.get_attr(~"href") { alt elmt.get_attr(~"href") {
some(filename) { some(filename) {
#debug["Linking to a css sheet named: %s", filename]; #debug["Linking to a css sheet named: %s", filename];
style_chan.send(file(copy filename)); style_chan.send(File(copy filename));
} }
none { /* fall through*/ } none { /* fall through*/ }
} }
@ -189,7 +191,7 @@ fn build_dom(scope: NodeScope, stream: port<Token>) -> (Node, port<Stylesheet>)
} }
} }
style_chan.send(exit); style_chan.send(Exit);
ret (cur_node, style_port); ret (cur_node, style_port);
} }

Просмотреть файл

@ -1,3 +1,5 @@
#[doc = "A collection of functions that are useful for both css and html parsing."]
import option::is_none; import option::is_none;
import str::from_bytes; import str::from_bytes;
import vec::push; import vec::push;
@ -17,7 +19,6 @@ trait u8_methods {
fn is_alpha() -> bool; fn is_alpha() -> bool;
} }
impl u8_methods of u8_methods for u8 { impl u8_methods of u8_methods for u8 {
fn is_whitespace() -> bool { fn is_whitespace() -> bool {
ret self == ' ' as u8 || self == '\n' as u8 || self == '\t' as u8; ret self == ' ' as u8 || self == '\n' as u8 || self == '\t' as u8;
@ -67,34 +68,26 @@ impl util_methods of util_methods for InputState {
fn expect(ch: u8) { fn expect(ch: u8) {
alt self.get() { alt self.get() {
CoeChar(c) { CoeChar(c) { if c != ch { self.parse_err(#fmt("expected '%c'", ch as char)); } }
if c != ch { CoeEof { self.parse_err(#fmt("expected '%c' at eof", ch as char)); }
self.parse_err(#fmt("expected '%c'", ch as char));
}
}
CoeEof {
self.parse_err(#fmt("expected '%c' at eof", ch as char));
}
} }
} }
fn parse_ident() -> ~str { fn parse_ident() -> ~str {
let mut result: ~[u8] = ~[]; let mut result: ~[u8] = ~[];
loop { loop {
alt self.get() { alt self.get() {
CoeChar(c) { CoeChar(c) {
if (c.is_alpha()) { if (c.is_alpha()) { push(result, c); }
push(result, c); else if result.len() == 0u { self.parse_err(~"expected ident"); }
} else if result.len() == 0u { else {
self.parse_err(~"expected ident"); self.unget(c);
} else { break;
self.unget(c);
break;
}
}
CoeEof {
self.parse_err(~"expected ident");
} }
}
CoeEof {
self.parse_err(~"expected ident");
}
} }
} }
ret str::from_bytes(result); ret str::from_bytes(result);
@ -110,15 +103,15 @@ impl util_methods of util_methods for InputState {
fn eat_whitespace() { fn eat_whitespace() {
loop { loop {
alt self.get() { alt self.get() {
CoeChar(c) { CoeChar(c) {
if !c.is_whitespace() { if !c.is_whitespace() {
self.unget(c); self.unget(c);
ret;
}
}
CoeEof {
ret; ret;
} }
}
CoeEof {
ret;
}
} }
} }
} }

Просмотреть файл

@ -0,0 +1,98 @@
#[doc = "Helper functions to parse values of specific attributes."]
import dom::style::*;
import str::{pop_char, from_chars};
import float::from_str;
import option::map;
export parse_font_size;
export parse_size;
export parse_display_type;
fn parse_unit(str : ~str) -> option<Unit> {
alt str {
s if s.ends_with(~"%") { from_str(str.substr(0, str.len() - 1)).map(|f| Percent(f)) }
s if s.ends_with(~"in") { from_str(str.substr(0, str.len() - 2)).map(|f| In(f)) }
s if s.ends_with(~"cm") { from_str(str.substr(0, str.len() - 2)).map(|f| Cm(f)) }
s if s.ends_with(~"mm") { from_str(str.substr(0, str.len() - 2)).map(|f| Mm(f)) }
s if s.ends_with(~"pt") { from_str(str.substr(0, str.len() - 2)).map(|f| Pt(f)) }
s if s.ends_with(~"pc") { from_str(str.substr(0, str.len() - 2)).map(|f| Pc(f)) }
s if s.ends_with(~"px") { from_str(str.substr(0, str.len() - 2)).map(|f| Px(f)) }
s if s.ends_with(~"em") { from_str(str.substr(0, str.len() - 2)).map(|f| Em(f)) }
s if s.ends_with(~"ex") { from_str(str.substr(0, str.len() - 2)).map(|f| Ex(f)) }
_ { none }
}
}
fn parse_font_size(str : ~str) -> option<Unit> {
// The default pixel size, not sure if this is accurate.
let default = 16.0;
alt str {
~"xx-small" { some(Px(0.6*default)) }
~"x-small" { some(Px(0.75*default)) }
~"small" { some(Px(8.0/9.0*default)) }
~"medium" { some(Px(default)) }
~"large" { some(Px(1.2*default)) }
~"x-large" { some(Px(1.5*default)) }
~"xx-large" { some(Px(2.0*default)) }
~"smaller" { some(Em(0.8)) }
~"larger" { some(Em(1.25)) }
~"inherit" { some(Em(1.0)) }
_ { parse_unit(str) }
}
}
// For width / height, and anything else with the same attribute values
fn parse_size(str : ~str) -> option<Unit> {
alt str {
~"auto" { some(Auto) }
~"inherit" { some(Em(1.0)) }
_ { parse_unit(str) }
}
}
fn parse_display_type(str : ~str) -> option<DisplayType> {
alt str {
~"inline" { some(DisInline) }
~"block" { some(DisBlock) }
~"none" { some(DisNone) }
_ { #debug["Recieved unknown display value '%s'", str]; none }
}
}
#[cfg(test)]
mod test {
import css_lexer::spawn_css_lexer_from_string;
import css_builder::build_stylesheet;
#[test]
fn should_match_font_sizes() {
let input = ~"* {font-size:12pt; font-size:inherit; font-size:2em; font-size:x-small}";
let token_port = spawn_css_lexer_from_string(input);
let actual_rule = build_stylesheet(token_port);
let expected_rule : Stylesheet = ~[~(~[~Element(~"*", ~[])],
~[FontSize(Pt(12.0)),
FontSize(Em(1.0)),
FontSize(Em(2.0)),
FontSize(Px(12.0))])];
assert actual_rule == expected_rule;
}
#[test]
fn should_match_width_height() {
let input = ~"* {width:20%; height:auto; width:20px; width:3in; height:70mm; height:3cm}";
let token_port = spawn_css_lexer_from_string(input);
let actual_rule = build_stylesheet(token_port);
let expected_rule : Stylesheet = ~[~(~[~Element(~"*", ~[])],
~[Width(Percent(20.0)),
Height(Auto),
Width(Px(20.0)),
Width(In(3.0)),
Height(Mm(70.0)),
Height(Cm(3.0))])];
assert actual_rule == expected_rule;
}
}

Просмотреть файл

@ -57,6 +57,7 @@ mod layout {
mod parser { mod parser {
mod lexer_util; mod lexer_util;
mod parser_util;
mod css_lexer; mod css_lexer;
mod html_lexer; mod html_lexer;
mod html_builder; mod html_builder;