Permalink
Browse files

Added the ability to compute heights from parent heights and to layou…

…t specified heights
  • Loading branch information...
1 parent 470a88f commit 95d09981490a5b49d4371ee2c985666f9ad36123 @mmeyerho mmeyerho committed Aug 7, 2012
View
23 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"
+ }
+}
View
59 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
View
27 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];
}
View
8 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)));
View
14 src/servo/layout/inline.rs
@@ -8,30 +8,32 @@ 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"];
// 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];
}
View
2 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));
View
97 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);
+ }
+}
View
1 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 {
View
2 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;
}
View
6 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;
}
View
18 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
}
}
View
27 src/servo/parser/parser_util.rs
@@ -12,14 +12,13 @@ export parse_display_type;
fn parse_unit(str : ~str) -> option<Unit> {
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<Unit> {
~"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<Unit> {
fn parse_size(str : ~str) -> option<Unit> {
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;
}
View
7 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%}
View
13 src/test/height_compute.html
@@ -0,0 +1,13 @@
+<head>
+ <link rel="stylesheet" href="height.css" />
+</head>
+<body>
+ <div class="start">
+ <div class="half"></div>
+ <div class="quarter"></div>
+ <div class="eighth"></div>
+ <div class="sixteenth"></div>
+ <div class="thirtysecond"></div>
+ <div class="sixtyfourth"></div>
+ </div>
+</body>

0 comments on commit 95d0998

Please sign in to comment.