diff --git a/src/servo/dom/style.rs b/src/servo/dom/style.rs index f74c2adb153..66a81da59bf 100644 --- a/src/servo/dom/style.rs +++ b/src/servo/dom/style.rs @@ -3,7 +3,7 @@ import util::color::Color; #[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, + http://www.w3.org/TR/CSS2/selector.html are represented by nested types. "] enum DisplayType { @@ -15,13 +15,8 @@ enum DisplayType { enum Unit { Auto, Percent(float), - In(float), Mm(float), - Cm(float), - Em(float), - Ex(float), Pt(float), - Pc(float), Px(float) } @@ -51,3 +46,19 @@ enum Selector{ type Rule = (~[~Selector], ~[StyleDeclaration]); type Stylesheet = ~[~Rule]; + +#[doc="Convert between units measured in millimeteres and pixels"] +pure fn MmToPx(u : Unit) -> Unit { + match u { + Mm(m) => Px(m * 3.7795), + _ => fail ~"Calling MmToPx on a unit that is not a Mm" + } +} + +#[doc="Convert between units measured in points and pixels"] +pure fn PtToPx(u : Unit) -> Unit { + match u { + Pt(m) => Px(m * 1.3333), + _ => fail ~"Calling PtToPx on a unit that is not a Pt" + } +} diff --git a/src/servo/layout/base.rs b/src/servo/layout/base.rs index 987a12b7da2..3caba60d58c 100644 --- a/src/servo/layout/base.rs +++ b/src/servo/layout/base.rs @@ -11,9 +11,10 @@ import geom::rect::Rect; import geom::size::Size2D; import image::base::{image, load}; import util::tree; -import util::color::{Color, css_colors}; -import style::style::SpecifiedStyle; +import util::color::Color; import text::TextBox; +import traverse::extended_full_traversal; +import style::style::{SpecifiedStyle}; import vec::{push, push_all}; import arc::{arc, clone}; @@ -155,8 +156,17 @@ impl BTree : tree::WriteMethods<@Box> { } } -// Private methods impl @Box { + #[doc="The main reflow routine."] + fn reflow() { + match self.kind { + BlockBox => self.reflow_block(), + InlineBox => self.reflow_inline(), + IntrinsicBox(size) => self.reflow_intrinsic(*size), + TextBoxKind(subbox) => self.reflow_text(subbox) + } + } + #[doc="Dumps the box tree, for debugging, with indentation."] fn dump_indent(indent: uint) { let mut s = ~""; @@ -173,16 +183,37 @@ impl @Box { } } -// Public methods +#[doc = " + Set your width to the maximum available width and return the + maximum available width any children can use. Currently children + are just given the same available width. +"] +fn give_kids_width(+available_width : au, box : @Box) -> au { + // TODO: give smaller available widths if the width of the + // containing box is constrained + match box.kind { + BlockBox | InlineBox => box.bounds.size.width = available_width, + IntrinsicBox(*) | TextBoxKind(*) => { } + } + + available_width +} + +#[doc="Wrapper around reflow so it can be passed to traverse"] +fn reflow_wrapper(b : @Box) { + b.reflow(); +} + impl @Box { - #[doc="The main reflow routine."] - fn reflow(available_width: au) { - match self.kind { - BlockBox => self.reflow_block(available_width), - InlineBox => self.reflow_inline(available_width), - IntrinsicBox(size) => self.reflow_intrinsic(*size), - TextBoxKind(subbox) => self.reflow_text(available_width, subbox) - } + #[doc=" + Run a parallel traversal over the layout tree rooted at + this box. On the top-down traversal give each box the + available width determined by their parent and on the + bottom-up traversal reflow each box based on their + attributes and their children's sizes. + "] + fn reflow_subtree(available_width : au) { + extended_full_traversal(self, available_width, give_kids_width, reflow_wrapper); } #[doc="The trivial reflow routine for instrinsically-sized frames."] @@ -200,7 +231,7 @@ impl @Box { // Debugging -trait PrivateNodeMethods { +trait PrivateNodeMethods{ fn dump_indent(ident: uint); } @@ -292,7 +323,7 @@ mod test { tree::add_child(BTree, b3, b1); tree::add_child(BTree, b3, b2); - b3.reflow_block(au(100)); + b3.reflow_subtree(au(100)); let fb = flat_bounds(b3); #debug["fb=%?", fb]; assert fb == ~[geometry::box(au(0), au(0), au(10), au(10)), // n0 diff --git a/src/servo/layout/block.rs b/src/servo/layout/block.rs index ca730fbbe01..7a59ba5d36c 100644 --- a/src/servo/layout/block.rs +++ b/src/servo/layout/block.rs @@ -1,19 +1,20 @@ #[doc="Block layout."] +import dom::style::{Px, Mm, Pt, Auto, Percent, Unit}; import geom::point::Point2D; import geom::size::Size2D; -import gfx::geometry::au; +import gfx::geometry::{px_to_au, au}; import util::tree; import base::{Box, BlockBox, BTree}; trait BlockLayoutMethods { - fn reflow_block(available_widh: au); + fn reflow_block(); } #[doc="The public block layout methods."] impl @Box : BlockLayoutMethods { #[doc="The main reflow routine for block layout."] - fn reflow_block(available_width: au) { + fn reflow_block() { assert self.kind == BlockBox; #debug["starting reflow block"]; @@ -24,19 +25,31 @@ impl @Box : BlockLayoutMethods { // This routine: // - generates root.bounds.size // - generates root.bounds.origin for each child - // - and recursively computes the bounds for each child let mut current_height = 0; + + // Find the combined height of all the children and mark the + // relative heights of the children in the box for tree::each_child(BTree, self) |c| { - let mut blk_available_width = available_width; // FIXME subtract borders, margins, etc c.bounds.origin = Point2D(au(0), au(current_height)); - c.reflow(blk_available_width); current_height += *c.bounds.size.height; } + let height = match self.appearance.height { + Px(p) => px_to_au(p.to_int()), + Auto => au(current_height), + _ => fail ~"inhereit_height failed, height is neither a Px or auto" + }; + // FIXME: Width is wrong in the calculation below. - self.bounds.size = Size2D(available_width, au(current_height)); + let width = match self.appearance.width { + Px(p) => px_to_au(p.to_int()), + Auto => self.bounds.size.width, // Do nothing here, width was set by top-down pass + _ => fail ~"inhereit_height failed, width is neither a Px or auto" + }; + + self.bounds.size = Size2D(width, height); #debug["reflow_block size=%?", copy self.bounds]; } diff --git a/src/servo/layout/display_list_builder.rs b/src/servo/layout/display_list_builder.rs index a27426b668e..988be2a3a5b 100644 --- a/src/servo/layout/display_list_builder.rs +++ b/src/servo/layout/display_list_builder.rs @@ -118,7 +118,7 @@ fn should_convert_text_boxes_to_solid_color_background_items() { let subbox = match check b.kind { TextBoxKind(subbox) => subbox }; - b.reflow_text(px_to_au(800), subbox); + b.reflow_text(subbox); let list = dvec(); box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0))); @@ -139,7 +139,7 @@ fn should_convert_text_boxes_to_text_items() { let subbox = match check b.kind { TextBoxKind(subbox) => { subbox } }; - b.reflow_text(px_to_au(800), subbox); + b.reflow_text(subbox); let list = dvec(); box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0))); @@ -159,7 +159,7 @@ fn should_calculate_the_bounds_of_the_text_box_background_color() { let subbox = match check b.kind { TextBoxKind(subbox) => { subbox } }; - b.reflow_text(px_to_au(800), subbox); + b.reflow_text(subbox); let list = dvec(); box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0))); @@ -181,7 +181,7 @@ fn should_calculate_the_bounds_of_the_text_items() { let subbox = match check b.kind { TextBoxKind(subbox) => { subbox } }; - b.reflow_text(px_to_au(800), subbox); + b.reflow_text(subbox); let list = dvec(); box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0))); diff --git a/src/servo/layout/inline.rs b/src/servo/layout/inline.rs index 265df9d0265..1df882d0cf8 100644 --- a/src/servo/layout/inline.rs +++ b/src/servo/layout/inline.rs @@ -8,12 +8,12 @@ import util::tree; import base::{Box, InlineBox, BTree}; trait InlineLayout { - fn reflow_inline(available_width: au); + fn reflow_inline(); } #[doc="The main reflow routine for inline layout."] impl @Box : InlineLayout { - fn reflow_inline(available_width: au) { + fn reflow_inline() { assert self.kind == InlineBox; #debug["starting reflow inline"]; @@ -21,17 +21,19 @@ impl @Box : InlineLayout { // FIXME: This is clownshoes inline layout and is not even close to // correct. let y = 0; - let mut x = 0, inline_available_width = *available_width; + let mut x = 0; let mut current_height = 0; + + // loop over children and set them at the proper horizontal offset for tree::each_child(BTree, self) |kid| { kid.bounds.origin = Point2D(au(x), au(y)); - kid.reflow(au(inline_available_width)); - inline_available_width -= *kid.bounds.size.width; x += *kid.bounds.size.width; current_height = int::max(current_height, *kid.bounds.size.height); } - self.bounds.size = Size2D(available_width, au(current_height)); + // The maximum available width should have been set in the top-down pass + self.bounds.size = Size2D(au(int::max(x, *self.bounds.size.width)), + au(current_height)); #debug["reflow_inline size=%?", copy self.bounds]; } diff --git a/src/servo/layout/layout_task.rs b/src/servo/layout/layout_task.rs index a8f0c425f8e..dc12fc31e84 100644 --- a/src/servo/layout/layout_task.rs +++ b/src/servo/layout/layout_task.rs @@ -42,7 +42,7 @@ fn Layout(renderer: Renderer) -> Layout { this_box.dump(); this_box.apply_css_style(); - this_box.reflow(px_to_au(800)); + this_box.reflow_subtree(px_to_au(800)); let dlist = build_display_list(this_box); renderer.send(renderer::RenderMsg(dlist)); diff --git a/src/servo/layout/style/apply.rs b/src/servo/layout/style/apply.rs index 98b7e202a37..337951d1563 100644 --- a/src/servo/layout/style/apply.rs +++ b/src/servo/layout/style/apply.rs @@ -1,24 +1,58 @@ #[doc="Applies the appropriate CSS style to boxes."] import dom::base::{Element, HTMLImageElement, Node}; -import either::right; +import dom::style::{Percent, Mm, Pt, Px, Auto, PtToPx, MmToPx}; +import gfx::geometry::au_to_px; import image::base::load; -import base::{Box, BTree, ImageHolder, LayoutData, NTree, SpecifiedStyle}; -import traverse::top_down_traversal; +import base::{Box, BTree, NTree, LayoutData, SpecifiedStyle, ImageHolder, + BlockBox, InlineBox, IntrinsicBox, TextBox}; +import traverse::{top_down_traversal}; trait ApplyStyleBoxMethods { fn apply_css_style(); fn apply_style(); } -#[doc="A wrapper so the function can be passed around by name."] -fn apply_style_wrapper(box : @Box) { +#[doc="A wrapper around a set of functions that can be applied as a top-down traversal of layout + boxes."] +fn inheritance_wrapper(box : @Box) { box.apply_style(); + inhereit_height(box); +} + +#[doc="Compute the specified height of a layout box based on it's css specification and its + parent's height."] +fn inhereit_height(box : @Box) { + let style = box.node.get_specified_style(); + + box.appearance.height = match style.height { + none => Auto, + some(h) => match h { + Auto | Px(*) => h, + Pt(*) => PtToPx(h), + Mm(*) => MmToPx(h), + Percent(em) => { + match box.tree.parent { + none => Auto, + some(parent) => { + match parent.appearance.height { + //This is a poorly constrained case, so we ignore the percentage + Auto => Auto, + Px(f) => Px(em*f/100.0), + Percent(*) | Mm(*) | Pt(*) => { + fail ~"failed inheriting heights, parent should only be Px or Auto" + } + } + } + } + } + } + } } impl @Box : ApplyStyleBoxMethods { fn apply_css_style() { - top_down_traversal(self, apply_style_wrapper); + top_down_traversal(self, inheritance_wrapper); } #[doc="Applies CSS style to a layout box. @@ -58,3 +92,54 @@ impl @Box : ApplyStyleBoxMethods { } } } + +#[cfg(test)] +mod test { + import dom::base::{Attr, HTMLDivElement, HTMLHeadElement, HTMLImageElement, ElementData}; + import dom::base::{NodeScope, UnknownElement}; + import dvec::dvec; + + #[allow(non_implicitly_copyable_typarams)] + fn new_node(scope: NodeScope, -name: ~str) -> Node { + let elmt = ElementData(name, ~HTMLDivElement); + return scope.new_node(base::Element(elmt)); + } + + #[test] + #[ignore(reason = "leaks memory")] + fn test_percent_height() { + let scope = NodeScope(); + + let parent = new_node(scope, ~"parent"); + let child = new_node(scope, ~"child"); + let child2 = new_node(scope, ~"child"); + let g1 = new_node(scope, ~"gchild"); + let g2 = new_node(scope, ~"gchild"); + + scope.add_child(parent, child); + scope.add_child(parent, child2); + scope.add_child(child, g1); + scope.add_child(child, g2); + parent.initialize_style_for_subtree(); + + do parent.aux |aux| { aux.specified_style.height = some(Px(100.0)); } + do child.aux |aux| { aux.specified_style.height = some(Auto); } + do child2.aux |aux| { aux.specified_style.height = some(Percent(50.0)); } + do g1.aux |aux| { aux.specified_style.height = some(Percent(50.0)); } + do g2.aux |aux| { aux.specified_style.height = some(Px(10.0)); } + + let parent_box = parent.construct_boxes(); + let child_box = parent_box.tree.first_child.get(); + let child2_box = parent_box.tree.last_child.get(); + let g1_box = child_box.tree.first_child.get(); + let g2_box = child_box.tree.last_child.get(); + + top_down_traversal(parent_box, inhereit_height); + + assert parent_box.appearance.height == Px(100.0); + assert child_box.appearance.height == Auto; + assert child2_box.appearance.height == Px(50.0); + assert g1_box.appearance.height == Auto; + assert g2_box.appearance.height == Px(10.0); + } +} diff --git a/src/servo/layout/style/matching.rs b/src/servo/layout/style/matching.rs index 16460c0b6b1..78abd4bb2fa 100644 --- a/src/servo/layout/style/matching.rs +++ b/src/servo/layout/style/matching.rs @@ -212,7 +212,6 @@ mod test { import dom::base::{Attr, HTMLDivElement, HTMLHeadElement, HTMLImageElement}; import dom::base::{NodeScope, UnknownElement}; import dvec::dvec; - import io::println; #[allow(non_implicitly_copyable_typarams)] fn new_node_from_attr(scope: NodeScope, -name: ~str, -val: ~str) -> Node { diff --git a/src/servo/layout/style/style.rs b/src/servo/layout/style/style.rs index 266c1503bef..a3b566fea38 100644 --- a/src/servo/layout/style/style.rs +++ b/src/servo/layout/style/style.rs @@ -127,7 +127,7 @@ impl Node : StyleMethods { "] fn get_specified_style() -> SpecifiedStyle { if !self.has_aux() { - fail ~"get_computed_style() called on a node without a style!"; + fail ~"get_specified_style() called on a node without a style!"; } return copy *self.aux(|x| copy x).specified_style; } diff --git a/src/servo/layout/text.rs b/src/servo/layout/text.rs index 9768f5d8a6c..5e0616d6dd0 100644 --- a/src/servo/layout/text.rs +++ b/src/servo/layout/text.rs @@ -17,12 +17,12 @@ struct TextBox { } trait TextLayout { - fn reflow_text(_available_width: au, subbox: @TextBox); + fn reflow_text(subbox: @TextBox); } #[doc="The main reflow routine for text layout."] impl @Box : TextLayout { - fn reflow_text(_available_width: au, subbox: @TextBox) { + fn reflow_text(subbox: @TextBox) { match self.kind { TextBoxKind(*) => { /* ok */ } _ => { fail ~"expected text box in reflow_text!" } @@ -51,7 +51,7 @@ fn should_calculate_the_size_of_the_text_box() { let b = n.construct_boxes(); let subbox = match check b.kind { TextBoxKind(subbox) => { subbox } }; - b.reflow_text(px_to_au(800), subbox); + b.reflow_text(subbox); let expected = Size2D(px_to_au(84), px_to_au(20)); assert b.bounds.size == expected; } diff --git a/src/servo/parser/html_builder.rs b/src/servo/parser/html_builder.rs index 13e95f37fdb..1eae54d7a9f 100644 --- a/src/servo/parser/html_builder.rs +++ b/src/servo/parser/html_builder.rs @@ -67,15 +67,15 @@ fn link_up_attribute(scope: NodeScope, node: Node, -key: ~str, -value: ~str) { fn build_element_kind(tag_name: ~str) -> ~ElementKind { match tag_name { - ~"div" => ~HTMLDivElement, - ~"img" => { - ~HTMLImageElement({ mut size: Size2D(geometry::px_to_au(100), - geometry::px_to_au(100)) - }) - } - ~"script" => ~HTMLScriptElement, - ~"head" => ~HTMLHeadElement, - _ => ~UnknownElement + ~"div" => ~HTMLDivElement, + ~"img" => { + ~HTMLImageElement({ mut size: Size2D(geometry::px_to_au(100), + geometry::px_to_au(100)) + }) + } + ~"script" => ~HTMLScriptElement, + ~"head" => ~HTMLHeadElement, + _ => ~UnknownElement } } diff --git a/src/servo/parser/parser_util.rs b/src/servo/parser/parser_util.rs index e68132beb6b..099584dac2b 100644 --- a/src/servo/parser/parser_util.rs +++ b/src/servo/parser/parser_util.rs @@ -12,14 +12,13 @@ export parse_display_type; fn parse_unit(str : ~str) -> option { match 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(~"in") => from_str(str.substr(0, str.len() - 2)).map(|f| Pt(72.0*f)), + s if s.ends_with(~"cm") => from_str(str.substr(0, str.len() - 2)).map(|f| Mm(10.0*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(~"pc") => from_str(str.substr(0, str.len() - 2)).map(|f| Pt(12.0*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)), + s if s.ends_with(~"ex") | s.ends_with(~"em") => fail ~"Em and Ex sizes not yet supported", _ => none, } } @@ -36,9 +35,9 @@ fn parse_font_size(str : ~str) -> option { ~"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)), + ~"smaller" => some(Percent(80.0)), + ~"larger" => some(Percent(125.0)), + ~"inherit" => some(Percent(100.0)), _ => parse_unit(str), } } @@ -47,7 +46,7 @@ fn parse_font_size(str : ~str) -> option { fn parse_size(str : ~str) -> option { match str { ~"auto" => some(Auto), - ~"inherit" => some(Em(1.0)), + ~"inherit" => some(Percent(100.0)), _ => parse_unit(str), } } @@ -68,13 +67,13 @@ mod test { #[test] fn should_match_font_sizes() { - let input = ~"* {font-size:12pt; font-size:inherit; font-size:2em; font-size:x-small}"; + let input = ~"* {font-size:12pt; font-size:inherit; font-size:200%; 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(Percent(100.0)), + FontSize(Percent(200.0)), FontSize(Px(12.0))])]; assert actual_rule == expected_rule; @@ -89,9 +88,9 @@ mod test { ~[Width(Percent(20.0)), Height(Auto), Width(Px(20.0)), - Width(In(3.0)), + Width(Pt(216.0)), Height(Mm(70.0)), - Height(Cm(3.0))])]; + Height(Mm(30.0))])]; assert actual_rule == expected_rule; } diff --git a/src/test/height.css b/src/test/height.css new file mode 100644 index 00000000000..41031e2a517 --- /dev/null +++ b/src/test/height.css @@ -0,0 +1,7 @@ +.start {background-color : gray; height : 600px} +.half {background-color : red; height : 50%} +.quarter {background-color : rgb(250, 125, 0); height : 25%} +.eighth {background-color : yellow; height : 12.5%} +.sixteenth {background-color : green; height : 6.25%} +.thirtysecond {background-color : blue; height : 3.125%} +.sixtyfourth {background-color : purple; height : 1.5625%} diff --git a/src/test/height_compute.html b/src/test/height_compute.html new file mode 100644 index 00000000000..89734e50fdc --- /dev/null +++ b/src/test/height_compute.html @@ -0,0 +1,13 @@ + + + + +
+
+
+
+
+
+
+
+