From 1095c9e4ff8871562a1f5a44c2e4086613418b00 Mon Sep 17 00:00:00 2001 From: "Brian J. Burg" Date: Wed, 12 Sep 2012 09:52:01 -0700 Subject: [PATCH] Rewrite the layout tree to use tandem flow/box trees. Flows are responsible for determining layout positioning based on formatting context (block, inline, absolute, etc), while boxes are relatively dumb, responsible only for reporting content-specific metrics to the coordinating flow context, and creating display lists of oneself. This design is experimental and may be axed in favor of a traditional, single-tree layout data structure if it doesn't work out. --- src/servo/css/resolve/apply.rs | 99 +--- src/servo/css/resolve/matching.rs | 14 +- src/servo/css/styles.rs | 17 +- src/servo/dom/base.rs | 57 +- src/servo/dom/bindings/element.rs | 6 +- src/servo/gfx/geometry.rs | 45 +- src/servo/gfx/render_task.rs | 11 +- src/servo/html/dom_builder.rs | 12 +- src/servo/html/hubbub_html_parser.rs | 8 +- src/servo/image.rs | 9 + src/servo/image/holder.rs | 92 ++++ src/servo/layout/base.rs | 632 ++++++++++++----------- src/servo/layout/block.rs | 186 +++++-- src/servo/layout/box_builder.rs | 356 +++++++------ src/servo/layout/display_list_builder.rs | 94 ++-- src/servo/layout/inline.rs | 155 ++++-- src/servo/layout/layout_task.rs | 54 +- src/servo/layout/root.rs | 62 +++ src/servo/layout/text.rs | 47 +- src/servo/layout/traverse.rs | 32 ++ src/servo/layout/traverse_parallel.rs | 6 +- src/servo/servo.rc | 9 +- src/servo/text/shaper.rs | 13 +- src/servo/text/text_run.rs | 21 +- 24 files changed, 1205 insertions(+), 832 deletions(-) create mode 100644 src/servo/image.rs create mode 100644 src/servo/image/holder.rs create mode 100644 src/servo/layout/root.rs create mode 100644 src/servo/layout/traverse.rs diff --git a/src/servo/css/resolve/apply.rs b/src/servo/css/resolve/apply.rs index 2805ee030fd..2d1205d7541 100644 --- a/src/servo/css/resolve/apply.rs +++ b/src/servo/css/resolve/apply.rs @@ -1,11 +1,11 @@ #[doc="Applies the appropriate CSS style to boxes."] -use gfx::geometry::au_to_px; -use layout::base::{Box, BTree, NTree, LayoutData, SpecifiedStyle, ImageHolder, - BlockBox, InlineBox, IntrinsicBox, TextBox}; -use layout::traverse_parallel::{top_down_traversal}; -use std::net::url::Url; +use au = gfx::geometry; +use layout::base::{Box, SpecifiedStyle}; +use layout::traverse_parallel::top_down_traversal; +use image::ImageHolder; use resource::image_cache_task::ImageCacheTask; +use std::net::url::Url; use css::values::*; @@ -37,7 +37,7 @@ struct StyleApplicator { reflow: fn~(), } - +// TODO: normalize this into a normal preorder tree traversal function fn apply_style(box: @Box, doc_url: &Url, image_cache_task: ImageCacheTask, reflow: fn~()) { let applicator = StyleApplicator { box: box, @@ -61,24 +61,19 @@ fn inheritance_wrapper(box : @Box, doc_url: &Url, image_cache_task: ImageCacheTa reflow: reflow }; applicator.apply_style(); - inherit_fontsize(box); - inherit_height(box); - inherit_width(box); } -/* Turns symbolic (abs, rel) and relative font sizes into absolute lengths */ - fn inherit_fontsize(box : @Box) { - // TODO: complete this - return - } +/* +fn resolve_fontsize(box : @Box) { + // TODO: complete this + return +} -#[doc="Compute the specified height of a layout box based on it's css specification and its - parent's height."] -fn inherit_height(box : @Box) { - let style = box.node.get_specified_style(); +fn resolve_height(box : @Box) -> au { + let style = box.node.get_style(); let inherit_val = match box.tree.parent { - None => style.height.initial(), - Some(node) => node.appearance.height + None => au(0), + Some(parent) => parent.data.computed_size.height }; box.appearance.height = match style.height { @@ -91,9 +86,7 @@ fn inherit_height(box : @Box) { } } -#[doc="Compute the specified width of a layout box based on it's css specification and its - parent's width."] -fn inherit_width(box : @Box) { +fn resolve_width(box : @Box) { let style = box.node.get_specified_style(); let inherit_val = match box.tree.parent { None => style.height.initial(), @@ -108,7 +101,7 @@ fn inherit_width(box : @Box) { BoxLength(Em(n)) => BoxLength(Px(n * box.appearance.font_size.abs())) } } -} +}*/ impl StyleApplicator { fn apply_css_style() { @@ -141,7 +134,7 @@ impl StyleApplicator { // FIXME: Some sort of BASE HREF support! // FIXME: Parse URLs! let new_url = make_url(option::unwrap(url), Some(copy *self.doc_url)); - self.box.appearance.background_image = Some(ImageHolder(new_url, self.image_cache_task, self.reflow)) + self.box.data.background_image = Some(ImageHolder(new_url, self.image_cache_task, self.reflow)) }; } _ => { /* Ignore. */ } @@ -155,57 +148,5 @@ impl StyleApplicator { #[cfg(test)] mod test { - use dom::base::{Attr, HTMLDivElement, HTMLHeadElement, HTMLImageElement, ElementData}; - use dom::base::{NodeScope, Node, UnknownElement}; - use dvec::DVec; - - #[allow(non_implicitly_copyable_typarams)] - fn new_node(scope: NodeScope, -name: ~str) -> Node { - let elmt = ElementData(name, ~HTMLDivElement); - return scope.new_node(dom::base::Element(elmt)); - } - - #[test] - #[ignore(reason = "busted")] - 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); - let _handles = parent.initialize_style_for_subtree(); - - // TODO: use helper methods to create test values - let px100 = BoxLength(Px(100.0)); - let px10 = BoxLength(Px(10.0)); - let px50 = BoxLength(Px(50.0)); - let pc50 = BoxPercent(50.0); - - do parent.aux |aux| { aux.specified_style.height = Specified(px100); } - do child.aux |aux| { aux.specified_style.height = Specified(BoxAuto); } - do child2.aux |aux| { aux.specified_style.height = Specified(pc50); } - do g1.aux |aux| { aux.specified_style.height = Specified(pc50); } - do g2.aux |aux| { aux.specified_style.height = Specified(px10); } - - let parent_box = parent.construct_boxes(); - let child_box = parent_box.get().tree.first_child.get(); - let child2_box = parent_box.get().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.get(), inherit_height); - - assert parent_box.get().appearance.height == px100; - assert child_box.appearance.height == BoxAuto; - assert child2_box.appearance.height == px50; - assert g1_box.appearance.height == BoxAuto; - assert g2_box.appearance.height == px10; - } -} + /* TODO: rewrite once cascade and resolve written. */ +} \ No newline at end of file diff --git a/src/servo/css/resolve/matching.rs b/src/servo/css/resolve/matching.rs index c2cfb0d3d33..7846212403e 100644 --- a/src/servo/css/resolve/matching.rs +++ b/src/servo/css/resolve/matching.rs @@ -167,12 +167,12 @@ impl Node : PrivStyleMethods { fn update_style(decl : StyleDeclaration) { self.aux(|layout| { match decl { - BackgroundColor(col) => layout.specified_style.background_color = col, - Display(dis) => layout.specified_style.display_type = dis, - FontSize(size) => layout.specified_style.font_size = size, - Height(size) => layout.specified_style.height = size, - Color(col) => layout.specified_style.text_color = col, - Width(size) => layout.specified_style.width = size + BackgroundColor(col) => layout.style.background_color = col, + Display(dis) => layout.style.display_type = dis, + FontSize(size) => layout.style.font_size = size, + Height(size) => layout.style.height = size, + Color(col) => layout.style.text_color = col, + Width(size) => layout.style.width = size }; }) } @@ -202,7 +202,7 @@ impl Node : MatchingMethods { } } - self.aux(|a| #debug["Changed the style to: %?", copy *a.specified_style]); + self.aux(|a| #debug["Changed the style to: %?", copy *a.style]); } } diff --git a/src/servo/css/styles.rs b/src/servo/css/styles.rs index 55b2de5eb64..9e4d4d89987 100644 --- a/src/servo/css/styles.rs +++ b/src/servo/css/styles.rs @@ -6,9 +6,10 @@ use css::values::*; use css::values::Stylesheet; use dom::base::{HTMLDivElement, HTMLHeadElement, HTMLImageElement, UnknownElement, HTMLScriptElement}; use dom::base::{Comment, Doctype, Element, Node, NodeKind, Text}; +use dom::base::{LayoutData}; use util::color::{Color, rgb}; use util::color::css_colors::{white, black}; -use layout::base::{LayoutData, NTree}; +use dom::base::NodeTree; type SpecifiedStyle = {mut background_color : CSSValue, mut display_type : CSSValue, @@ -97,7 +98,7 @@ impl Node : StylePriv { if !self.has_aux() { let node_kind = self.read(|n| copy *n.kind); let the_layout_data = @LayoutData({ - mut specified_style : ~empty_style_for_node_kind(node_kind), + mut style : ~empty_style_for_node_kind(node_kind), mut box : None }); @@ -112,7 +113,7 @@ impl Node : StylePriv { trait StyleMethods { fn initialize_style_for_subtree() -> ~[@LayoutData]; - fn get_specified_style() -> SpecifiedStyle; + fn style() -> SpecifiedStyle; fn recompute_style_for_subtree(styles : ARC); } @@ -121,7 +122,7 @@ impl Node : StyleMethods { fn initialize_style_for_subtree() -> ~[@LayoutData] { let mut handles = self.initialize_style(); - for NTree.each_child(self) |kid| { + for NodeTree.each_child(self) |kid| { handles += kid.initialize_style_for_subtree(); } @@ -134,11 +135,11 @@ impl Node : StyleMethods { TODO: Return a safe reference; don't copy. "] - fn get_specified_style() -> SpecifiedStyle { + fn style() -> SpecifiedStyle { if !self.has_aux() { - fail ~"get_specified_style() called on a node without a style!"; + fail ~"get_style() called on a node without a style!"; } - return copy *self.aux(|x| copy x).specified_style; + return copy *self.aux(|x| copy x).style; } #[doc=" @@ -152,7 +153,7 @@ impl Node : StyleMethods { let mut i = 0u; // Compute the styles of each of our children in parallel - for NTree.each_child(self) |kid| { + for NodeTree.each_child(self) |kid| { i = i + 1u; let new_styles = clone(&styles); diff --git a/src/servo/dom/base.rs b/src/servo/dom/base.rs index 3cd6d754e9c..302e3e54b21 100644 --- a/src/servo/dom/base.rs +++ b/src/servo/dom/base.rs @@ -1,21 +1,22 @@ #[doc="The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements."] -use gfx::geometry::au; -use geom::size::Size2D; -use layout::base::LayoutData; -use util::tree; -use js::rust::{bare_compartment, compartment, methods}; -use js::jsapi::{JSClass, JSObject, JSPropertySpec, JSContext, jsid, jsval, JSBool}; -use js::{JSPROP_ENUMERATE, JSPROP_SHARED}; -use js::crust::*; -use js::glue::bindgen::RUST_OBJECT_TO_JSVAL; -use dvec::DVec; -use ptr::null; -use dom::bindings; -use std::arc::ARC; -use css::values::Stylesheet; use comm::{Port, Chan}; use content::content_task::{ControlMsg, Timer}; +use css::styles::SpecifiedStyle; +use css::values::Stylesheet; +use dom::bindings; +use dvec::DVec; +use geom::size::Size2D; +use gfx::geometry::au; +use js::crust::*; +use js::glue::bindgen::RUST_OBJECT_TO_JSVAL; +use js::jsapi::{JSClass, JSObject, JSPropertySpec, JSContext, jsid, jsval, JSBool}; +use js::rust::{bare_compartment, compartment, methods}; +use js::{JSPROP_ENUMERATE, JSPROP_SHARED}; +use layout::base::Box; +use ptr::null; +use std::arc::ARC; +use util::tree; enum TimerControlMsg { Fire(~dom::bindings::window::TimerData), @@ -66,6 +67,20 @@ enum NodeData = { kind: ~NodeKind, }; + +/* The tree holding Nodes (read-only) */ +enum NodeTree { NodeTree } + +impl NodeTree : tree::ReadMethods { + fn each_child(node: Node, f: fn(Node) -> bool) { + tree::each_child(self, node, f) + } + + fn with_tree_fields(&&n: Node, f: fn(tree::Tree) -> R) -> R { + n.read(|n| f(n.tree)) + } +} + enum NodeKind { Doctype(DoctypeData), Comment(~str), @@ -148,10 +163,16 @@ enum ElementKind { HTMLScriptElement } -#[doc=" - The rd_aux data is a (weak) pointer to the layout data, which contains the CSS info as well as - the primary box. Note that there may be multiple boxes per DOM node. -"] + +/** The RCU rd_aux data is a (weak) pointer to the layout data, + defined by this `LayoutData` enum. It contains the CSS style object + as well as the primary `Box`. + + Note that there may be multiple boxes per DOM node. */ +enum LayoutData = { + mut style: ~SpecifiedStyle, + mut box: Option<@Box> +}; type Node = rcu::Handle; diff --git a/src/servo/dom/bindings/element.rs b/src/servo/dom/bindings/element.rs index 7bf59808d33..6f9afd3c7da 100644 --- a/src/servo/dom/bindings/element.rs +++ b/src/servo/dom/bindings/element.rs @@ -1,3 +1,4 @@ +use au = gfx::geometry; use js::rust::{bare_compartment, methods, jsobj}; use js::{JS_ARGV, JSCLASS_HAS_RESERVED_SLOTS, JSPROP_ENUMERATE, JSPROP_SHARED, JSVAL_NULL, JS_THIS_OBJECT, JS_SET_RVAL, JSPROP_NATIVE_ACCESSORS}; @@ -17,7 +18,6 @@ use ptr::null; use node::unwrap; use dom::base::{HTMLImageElement, HTMLScriptElement, HTMLHeadElement, HTMLDivElement, UnknownElement}; -use gfx::geometry::{au_to_px, px_to_au}; extern fn finalize(_fop: *JSFreeOp, obj: *JSObject) { #debug("element finalize!"); @@ -81,7 +81,7 @@ extern fn HTMLImageElement_getWidth(cx: *JSContext, _argc: c_uint, vp: *mut jsva } }); *vp = RUST_INT_TO_JSVAL( - (au_to_px(width) & (i32::max_value as int)) as libc::c_int); + (au::to_px(width) & (i32::max_value as int)) as libc::c_int); return 1; } @@ -99,7 +99,7 @@ extern fn HTMLImageElement_setWidth(cx: *JSContext, _argc: c_uint, vp: *mut jsva match ed.kind { ~HTMLImageElement(img) => { let arg = ptr::offset(JS_ARGV(cx, unsafe::reinterpret_cast(&vp)), 0); - img.size.width = px_to_au(RUST_JSVAL_TO_INT(*arg) as int) + img.size.width = au::from_px(RUST_JSVAL_TO_INT(*arg) as int) }, _ => fail ~"why is this not an image element?" } diff --git a/src/servo/gfx/geometry.rs b/src/servo/gfx/geometry.rs index 6184f19f7b8..4b9292d3701 100644 --- a/src/servo/gfx/geometry.rs +++ b/src/servo/gfx/geometry.rs @@ -14,52 +14,49 @@ impl au : Num { pure fn neg() -> au { au(-*self) } pure fn to_int() -> int { *self as int } + static pure fn from_int(n: int) -> au { au((n & (i32::max_value as int)) as i32) } } -impl au : cmp::Eq { - pure fn eq(&&other: au) -> bool { - *self == *other - } - pure fn ne(&&other: au) -> bool { - *self != *other - } +impl au : cmp::Ord { + pure fn lt(&&other: au) -> bool { *self < *other } + pure fn le(&&other: au) -> bool { *self <= *other } + pure fn ge(&&other: au) -> bool { *self >= *other } + pure fn gt(&&other: au) -> bool { *self > *other } } -impl au : cmp::Ord { - pure fn lt(&&other: au) -> bool { - *self < *other - } - pure fn le(&&other: au) -> bool { - *self <= *other - } - pure fn ge(&&other: au) -> bool { - *self >= *other - } - pure fn gt(&&other: au) -> bool { - *self > *other - } +impl au : cmp::Eq { + pure fn eq(&&other: au) -> bool { *self == *other } + pure fn ne(&&other: au) -> bool { *self != *other } } +pure fn min(x: au, y: au) -> au { if x < y { x } else { y } } +pure fn max(x: au, y: au) -> au { if x > y { x } else { y } } + fn box(x: A, y: A, w: A, h: A) -> Rect { Rect(Point2D(x, y), Size2D(w, h)) } -fn zero_rect_au() -> Rect { +fn zero_rect() -> Rect { let z = au(0); Rect(Point2D(z, z), Size2D(z, z)) } -fn zero_size_au() -> Size2D { + +fn zero_point() -> Point2D { + Point2D(au(0), au(0)) +} + +fn zero_size() -> Size2D { Size2D(au(0), au(0)) } -pure fn px_to_au(i: int) -> au { +pure fn from_px(i: int) -> au { from_int(i * 60) } -pure fn au_to_px(au: au) -> int { +pure fn to_px(au: au) -> int { (*au / 60) as int } diff --git a/src/servo/gfx/render_task.rs b/src/servo/gfx/render_task.rs index ec1e2b8746e..f2b1b3ab7fc 100644 --- a/src/servo/gfx/render_task.rs +++ b/src/servo/gfx/render_task.rs @@ -1,5 +1,6 @@ +use au = geometry; +use au::au; use platform::osmain; -use geometry::*; use comm::*; use image::base::Image; use dl = display_list; @@ -88,8 +89,8 @@ trait ToAzureRect { impl Rect : ToAzureRect { fn to_azure_rect() -> Rect { - Rect(Point2D(au_to_px(self.origin.x) as AzFloat, au_to_px(self.origin.y) as AzFloat), - Size2D(au_to_px(self.size.width) as AzFloat, au_to_px(self.size.height) as AzFloat)) + Rect(Point2D(au::to_px(self.origin.x) as AzFloat, au::to_px(self.origin.y) as AzFloat), + Size2D(au::to_px(self.size.width) as AzFloat, au::to_px(self.size.height) as AzFloat)) } } @@ -167,8 +168,8 @@ pub fn draw_glyphs(draw_target: &DrawTarget, bounds: Rect, text_run: &GlyphR let azglyph: AzGlyph = { mIndex: glyph.index as uint32_t, mPosition: { - x: au_to_px(origin.x.add(glyph.pos.offset.x)) as AzFloat, - y: au_to_px(origin.y.add(glyph.pos.offset.y)) as AzFloat + x: au::to_px(origin.x.add(glyph.pos.offset.x)) as AzFloat, + y: au::to_px(origin.y.add(glyph.pos.offset.y)) as AzFloat } }; origin = Point2D(origin.x.add(glyph.pos.advance.x), diff --git a/src/servo/html/dom_builder.rs b/src/servo/html/dom_builder.rs index d49bf1a1a4a..a1cb56e0684 100644 --- a/src/servo/html/dom_builder.rs +++ b/src/servo/html/dom_builder.rs @@ -1,11 +1,11 @@ #[doc="Constructs a DOM tree from an incoming token stream."] +use au = gfx::geometry; +use au::au; use dom::base::{Attr, Element, ElementData, ElementKind, HTMLDivElement, HTMLHeadElement, HTMLScriptElement}; use dom::base::{HTMLImageElement, Node, NodeScope, Text, UnknownElement}; use geom::size::Size2D; -use gfx::geometry; -use gfx::geometry::au; use html::lexer; use html::lexer::Token; use css::values::Stylesheet; @@ -37,7 +37,7 @@ fn link_up_attribute(scope: NodeScope, node: Node, -key: ~str, -value: ~str) { None => { // Drop on the floor. } - Some(s) => { img.size.width = geometry::px_to_au(s); } + Some(s) => { img.size.width = au::from_px(s); } } } HTMLImageElement(img) if key == ~"height" => { @@ -46,7 +46,7 @@ fn link_up_attribute(scope: NodeScope, node: Node, -key: ~str, -value: ~str) { // Drop on the floor. } Some(s) => { - img.size.height = geometry::px_to_au(s); + img.size.height = au::from_px(s); } } } @@ -68,8 +68,8 @@ 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)) + ~HTMLImageElement({ mut size: Size2D(au::from_px(100), + au::from_px(100)) }) } ~"script" => ~HTMLScriptElement, diff --git a/src/servo/html/hubbub_html_parser.rs b/src/servo/html/hubbub_html_parser.rs index ae2f3335c17..0846540b622 100644 --- a/src/servo/html/hubbub_html_parser.rs +++ b/src/servo/html/hubbub_html_parser.rs @@ -1,9 +1,9 @@ +use au = gfx::geometry; use dom::base::{Attr, Comment, Doctype, DoctypeData, Element, ElementData, ElementKind}; use dom::base::{HTMLDivElement, HTMLHeadElement, HTMLImageElement, HTMLScriptElement}; use dom::base::{Node, NodeScope, Text, UnknownElement}; use css::values::Stylesheet; use geom::size::Size2D; -use gfx::geometry::px_to_au; use html::dom_builder::CSSMessage; use resource::resource_task::{Done, Load, Payload, ResourceTask}; use CSSExitMessage = html::dom_builder::Exit; @@ -118,7 +118,7 @@ fn build_element_kind(tag_name: &str) -> ~ElementKind { if tag_name == "div" { ~HTMLDivElement } else if tag_name == "img" { - ~HTMLImageElement({ mut size: Size2D(px_to_au(100), px_to_au(100)) }) + ~HTMLImageElement({ mut size: Size2D(au::from_px(100), au::from_px(100)) }) } else if tag_name == "script" { ~HTMLScriptElement } else if tag_name == "head" { @@ -187,13 +187,13 @@ fn parse_html(scope: NodeScope, url: Url, resource_task: ResourceTask) -> HtmlPa HTMLImageElement(img) if attribute.name == "width" => { match int::from_str(from_slice(attribute.value)) { None => {} // Drop on the floor. - Some(s) => img.size.width = px_to_au(s) + Some(s) => img.size.width = au::from_px(s) } } HTMLImageElement(img) if attribute.name == "height" => { match int::from_str(from_slice(attribute.value)) { None => {} // Drop on the floor. - Some(s) => img.size.height = px_to_au(s) + Some(s) => img.size.height = au::from_px(s) } } HTMLDivElement | HTMLImageElement(*) | HTMLHeadElement | diff --git a/src/servo/image.rs b/src/servo/image.rs new file mode 100644 index 00000000000..f01438dc3be --- /dev/null +++ b/src/servo/image.rs @@ -0,0 +1,9 @@ +/* This file exists just to make it easier to import things inside of + ./images/ without specifying the file they came out of imports. + +Note that you still must define each of the files as a module in +servo.rc. This is not ideal and may be changed in the future. */ + +pub use holder::ImageHolder; +pub use base::Image; + diff --git a/src/servo/image/holder.rs b/src/servo/image/holder.rs new file mode 100644 index 00000000000..b3f1c104e1e --- /dev/null +++ b/src/servo/image/holder.rs @@ -0,0 +1,92 @@ +use std::net::url::Url; +use std::arc::{ARC, clone}; +use resource::image_cache_task::ImageCacheTask; +use resource::image_cache_task; + +/** A struct to store image data. The image will be loaded once, the + first time it is requested, and an arc will be stored. Clones of + this arc are given out on demand. + */ +pub struct ImageHolder { + // Invariant: at least one of url and image is not none, except + // occasionally while get_image is being called + mut url : Option, + mut image : Option>, + image_cache_task: ImageCacheTask, + reflow_cb: fn~(), + +} + +fn ImageHolder(-url : Url, image_cache_task: ImageCacheTask, cb: fn~()) -> ImageHolder { + let holder = ImageHolder { + url : Some(copy url), + image : None, + image_cache_task : image_cache_task, + reflow_cb : copy cb, + }; + + // Tell the image cache we're going to be interested in this url + // FIXME: These two messages must be sent to prep an image for use + // but they are intended to be spread out in time. Ideally prefetch + // should be done as early as possible and decode only once we + // are sure that the image will be used. + image_cache_task.send(image_cache_task::Prefetch(copy url)); + image_cache_task.send(image_cache_task::Decode(move url)); + + holder +} + +impl ImageHolder { + // This function should not be called by two tasks at the same time + fn get_image() -> Option> { + // If this is the first time we've called this function, load + // the image and store it for the future + if self.image.is_none() { + assert self.url.is_some(); + + let mut temp = None; + temp <-> self.url; + let url = option::unwrap(temp); + + let response_port = Port(); + self.image_cache_task.send(image_cache_task::GetImage(copy url, response_port.chan())); + self.image = match response_port.recv() { + image_cache_task::ImageReady(image) => Some(clone(&image)), + image_cache_task::ImageNotReady => { + // Need to reflow when the image is available + let image_cache_task = self.image_cache_task; + let reflow = copy self.reflow_cb; + do task::spawn |copy url, move reflow| { + let response_port = Port(); + image_cache_task.send(image_cache_task::WaitForImage(copy url, response_port.chan())); + match response_port.recv() { + image_cache_task::ImageReady(*) => reflow(), + image_cache_task::ImageNotReady => fail /*not possible*/, + image_cache_task::ImageFailed => () + } + } + None + } + image_cache_task::ImageFailed => { + #info("image was not ready for %s", url.to_str()); + // FIXME: Need to schedule another layout when the image is ready + None + } + }; + } + + if self.image.is_some() { + // Temporarily swap out the arc of the image so we can clone + // it without breaking purity, then put it back and return the + // clone. This is not threadsafe. + let mut temp = None; + temp <-> self.image; + let im_arc = option::unwrap(temp); + self.image = Some(clone(&im_arc)); + + return Some(im_arc); + } else { + return None; + } + } +} \ No newline at end of file diff --git a/src/servo/layout/base.rs b/src/servo/layout/base.rs index 663195f81f2..e36bbab296a 100644 --- a/src/servo/layout/base.rs +++ b/src/servo/layout/base.rs @@ -1,58 +1,184 @@ -#[doc="Fundamental layout structures and algorithms."] +/* Fundamental layout structures and algorithms. */ +use au = gfx::geometry; +use core::dvec::DVec; +use core::to_str::ToStr; +use core::rand; use css::styles::SpecifiedStyle; -use css::values::{BoxSizing, Length, Px}; -use dom::base::{Element, ElementKind, HTMLDivElement, HTMLImageElement, Node, NodeData}; -use dom::base::{NodeKind}; +use css::values::{BoxSizing, Length, Px, CSSDisplay}; +use dom::base::{Element, ElementKind, HTMLDivElement, HTMLImageElement}; +use dom::base::{Node, NodeData, NodeKind, NodeTree}; use dom::rcu; -use gfx::geometry; -use gfx::geometry::{au, zero_size_au}; -use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; -use image::base::Image; -use util::tree; -use util::color::Color; -use text::TextBox; -use traverse_parallel::extended_full_traversal; -use vec::{push, push_all}; -use std::net::url::Url; -use resource::image_cache_task; -use image_cache_task::ImageCacheTask; -use core::to_str::ToStr; +use gfx::geometry::au; +use image::{Image, ImageHolder}; +use layout::block::BlockFlowData; +use layout::inline::InlineFlowData; +use layout::root::RootFlowData; +use layout::text::TextBoxData; +use servo_text::text_run::TextRun; use std::arc::{ARC, clone}; +use std::net::url::Url; use task::spawn; +use util::color::Color; +use util::tree; +use vec::{push, push_all}; -enum BoxKind { - BlockBox, - InlineBox, - IntrinsicBox(@Size2D), - TextBoxKind(@TextBox) +/* The type of the formatting context, and data specific to each +context, such as lineboxes or float lists */ +enum FlowContextData { + AbsoluteFlow, + BlockFlow(BlockFlowData), + FloatFlow, + InlineBlockFlow, + InlineFlow(InlineFlowData), + RootFlow(RootFlowData), + TableFlow } -impl BoxKind : cmp::Eq { - pure fn eq(&&other: BoxKind) -> bool { - match (self, other) { - (BlockBox, BlockBox) => true, - (InlineBox, InlineBox) => true, - _ => fail ~"unimplemented case in BoxKind.eq" +/* A particular kind of layout context. It manages the positioning of + layout boxes within the context. + + Flow contexts form a tree that is induced by the structure of the + box tree. Each context is responsible for laying out one or more + boxes, according to the flow type. The number of flow contexts should + be much fewer than the number of boxes. The context maintains a vector + of its constituent boxes in their document order. +*/ +struct FlowContext { + kind: FlowContextData, + data: FlowLayoutData, + /* reference to parent, children flow contexts */ + tree: tree::Tree<@FlowContext>, + /* TODO: debug only */ + mut id: int +} + +fn FlowContext(id: int, kind: FlowContextData, tree: tree::Tree<@FlowContext>) -> FlowContext { + FlowContext { + kind: kind, + data: FlowLayoutData(), + tree: tree, + id: id + } +} + +impl @FlowContext : cmp::Eq { + pure fn eq(&&other: @FlowContext) -> bool { box::ptr_eq(self, other) } + pure fn ne(&&other: @FlowContext) -> bool { !box::ptr_eq(self, other) } +} + +/* A box's kind influences how its styles are interpreted during + layout. For example, replaced content such as images are resized + differently than tables, text, or other content. + + It also holds data specific to different box types, such as text. +*/ +enum BoxData { + GenericBox, + ImageBox(Size2D), + TextBox(TextBoxData) +} + +struct Box { + /* references to children, parent */ + tree : tree::Tree<@Box>, + /* originating DOM node */ + node : Node, + /* reference to containing flow context, which this box + participates in */ + ctx : @FlowContext, + /* results of flow computation */ + data : BoxLayoutData, + /* kind tag and kind-specific data */ + kind : BoxData, + /* TODO: debug only */ + mut id: int +} + +fn Box(id: int, node: Node, ctx: @FlowContext, kind: BoxData) -> Box { + Box { + /* will be set when box is parented */ + tree : tree::empty(), + node : node, + ctx : ctx, + data : BoxLayoutData(), + kind : kind, + id : id + } +} + +impl @Box { + pure fn is_replaced() -> bool { + match self.kind { + ImageBox(*) => true, // TODO: form elements, etc + _ => false } } - pure fn ne(&&other: BoxKind) -> bool { - return !self.eq(other); + + pure fn get_min_width() -> au { + match self.kind { + // TODO: this should account for min/pref widths of the + // box element in isolation. That includes + // border/margin/padding but not child widths. The block + // FlowContext will combine the width of this element and + // that of its children to arrive at the context width. + GenericBox => au(0), + // TODO: If image isn't available, consult Node + // attrs, etc. to determine intrinsic dimensions. These + // dimensions are not defined by CSS 2.1, but are defined + // by the HTML5 spec in Section 4.8.1 + ImageBox(size) => size.width, + // TODO: account for line breaks, etc. The run should know + // how to compute its own min and pref widths, and should + // probably cache them. + TextBox(d) => d.runs.foldl(au(0), |sum, run| { + au::max(sum, run.min_break_width()) + }) + } } -} -struct Appearance { - mut background_image: Option, - // TODO: create some sort of layout-specific enum to differentiate between - // relative and resolved values. - mut width: BoxSizing, - mut height: BoxSizing, - mut font_size: Length, -} + pure fn get_pref_width() -> au { + match self.kind { + // TODO: this should account for min/pref widths of the + // box element in isolation. That includes + // border/margin/padding but not child widths. The block + // FlowContext will combine the width of this element and + // that of its children to arrive at the context width. + GenericBox => au(0), + // TODO: If image isn't available, consult Node + // attrs, etc. to determine intrinsic dimensions. These + // dimensions are not defined by CSS 2.1, but are defined + // by the HTML5 spec in Section 4.8.1 + ImageBox(size) => size.width, + // TODO: account for line breaks, etc. The run should know + // how to compute its own min and pref widths, and should + // probably cache them. + TextBox(d) => d.runs.foldl(au(0), |sum, run| { + au::max(sum, run.size().width) + }) + } + } + + /* Returns the amount of left, right "fringe" used by this + box. This should be based on margin, border, padding, width. */ + fn get_used_width() -> (au, au) { + // TODO: this should actually do some computation! + // See CSS 2.1, Section 10.3, 10.4. + + (au(0), au(0)) + } + + /* Returns the amount of left, right "fringe" used by this + box. This should be based on margin, border, padding, width. */ + fn get_used_height() -> (au, au) { + // TODO: this should actually do some computation! + // See CSS 2.1, Section 10.5, 10.6. + + (au(0), au(0)) + } -impl Appearance { // This will be very unhappy if it is getting run in parallel with // anything trying to read the background image fn get_image() -> Option> { @@ -61,155 +187,59 @@ impl Appearance { // Do a dance where we swap the ImageHolder out before we can // get the image out of it because we can't match against it // because holder.get_image() is not pure. - if (self.background_image).is_some() { + if (self.data.background_image).is_some() { let mut temp = None; - temp <-> self.background_image; + temp <-> self.data.background_image; let holder <- option::unwrap(temp); image = holder.get_image(); - self.background_image = Some(holder); + self.data.background_image = Some(holder); } - return image; + image } } +struct FlowLayoutData { + mut min_width: au, + mut pref_width: au, + mut position: Rect, +} -fn Appearance(kind: NodeKind) -> Appearance { - // TODO: these should come from initial() or elsewhere - Appearance { - font_size : Px(14.0), + +fn FlowLayoutData() -> FlowLayoutData { + FlowLayoutData { + min_width: au(0), + pref_width: au(0), + position : au::zero_rect(), + } +} + +struct BoxLayoutData { + mut min_width: au, + mut pref_width: au, + mut position: Rect, + + mut font_size: Length, + mut background_image: Option, +} + +fn BoxLayoutData() -> BoxLayoutData { + BoxLayoutData { + min_width: au(0), + pref_width: au(0), + position : au::zero_rect(), + + font_size : Px(0.0), background_image : None, - width : kind.default_width(), - height : kind.default_height(), } } -struct Box { - tree: tree::Tree<@Box>, - node: Node, - kind: BoxKind, - mut bounds: Rect, - appearance: Appearance, -} +// FIXME: Why do these have to be redefined for each node type? -fn Box(node: Node, kind: BoxKind) -> Box { - Box { - appearance : node.read(|n| Appearance(*n.kind)), - tree : tree::empty(), - node : node, - kind : kind, - bounds : geometry::zero_rect_au(), - } -} +/* The tree holding boxes */ +enum BoxTree { BoxTree } -#[doc="A struct to store image data. The image will be loaded once, - the first time it is requested, and an arc will be stored. Clones of - this arc are given out on demand."] -struct ImageHolder { - // Invariant: at least one of url and image is not none, except - // occasionally while get_image is being called - mut url : Option, - mut image : Option>, - image_cache_task: ImageCacheTask, - reflow: fn~(), - -} - -fn ImageHolder(-url : Url, image_cache_task: ImageCacheTask, reflow: fn~()) -> ImageHolder { - let holder = ImageHolder { - url : Some(copy url), - image : None, - image_cache_task : image_cache_task, - reflow : copy reflow, - }; - - // Tell the image cache we're going to be interested in this url - // FIXME: These two messages must be sent to prep an image for use - // but they are intended to be spread out in time. Ideally prefetch - // should be done as early as possible and decode only once we - // are sure that the image will be used. - image_cache_task.send(image_cache_task::Prefetch(copy url)); - image_cache_task.send(image_cache_task::Decode(move url)); - - holder -} - -impl ImageHolder { - // This function should not be called by two tasks at the same time - fn get_image() -> Option> { - // If this is the first time we've called this function, load - // the image and store it for the future - if self.image.is_none() { - assert self.url.is_some(); - - let mut temp = None; - temp <-> self.url; - let url = option::unwrap(temp); - - let response_port = Port(); - self.image_cache_task.send(image_cache_task::GetImage(copy url, response_port.chan())); - self.image = match response_port.recv() { - image_cache_task::ImageReady(image) => Some(clone(&image)), - image_cache_task::ImageNotReady => { - // Need to reflow when the image is available - let image_cache_task = self.image_cache_task; - let reflow = copy self.reflow; - do spawn |copy url, move reflow| { - let response_port = Port(); - image_cache_task.send(image_cache_task::WaitForImage(copy url, response_port.chan())); - match response_port.recv() { - image_cache_task::ImageReady(*) => reflow(), - image_cache_task::ImageNotReady => fail /*not possible*/, - image_cache_task::ImageFailed => () - } - } - None - } - image_cache_task::ImageFailed => { - #info("image was not ready for %s", url.to_str()); - // FIXME: Need to schedule another layout when the image is ready - None - } - }; - } - - if self.image.is_some() { - // Temporarily swap out the arc of the image so we can clone - // it without breaking purity, then put it back and return the - // clone. This is not threadsafe. - let mut temp = None; - temp <-> self.image; - let im_arc = option::unwrap(temp); - self.image = Some(clone(&im_arc)); - - return Some(im_arc); - } else { - return None; - } - } -} - -enum LayoutData = { - mut specified_style: ~SpecifiedStyle, - mut box: Option<@Box> -}; - -// FIXME: This is way too complex! Why do these have to have dummy receivers? --pcw - -enum NTree { NTree } -impl NTree : tree::ReadMethods { - fn each_child(node: Node, f: fn(Node) -> bool) { - tree::each_child(self, node, f) - } - - fn with_tree_fields(&&n: Node, f: fn(tree::Tree) -> R) -> R { - n.read(|n| f(n.tree)) - } -} - -enum BTree { BTree } - -impl BTree : tree::ReadMethods<@Box> { +impl BoxTree : tree::ReadMethods<@Box> { fn each_child(node: @Box, f: fn(&&@Box) -> bool) { tree::each_child(self, node, f) } @@ -219,9 +249,10 @@ impl BTree : tree::ReadMethods<@Box> { } } -impl BTree : tree::WriteMethods<@Box> { - fn add_child(node: @Box, child: @Box) { - tree::add_child(self, node, child) +impl BoxTree : tree::WriteMethods<@Box> { + fn add_child(parent: @Box, child: @Box) { + assert !box::ptr_eq(parent, child); + tree::add_child(self, parent, child) } fn with_tree_fields(&&b: @Box, f: fn(tree::Tree<@Box>) -> R) -> R { @@ -229,111 +260,168 @@ impl BTree : tree::WriteMethods<@Box> { } } -impl @Box { - #[doc="The main reflow routine."] - fn reflow() { +/* The tree holding FlowContexts */ +enum FlowTree { FlowTree } + +impl FlowTree : tree::ReadMethods<@FlowContext> { + fn each_child(ctx: @FlowContext, f: fn(&&@FlowContext) -> bool) { + tree::each_child(self, ctx, f) + } + + fn with_tree_fields(&&b: @FlowContext, f: fn(tree::Tree<@FlowContext>) -> R) -> R { + f(b.tree) + } +} + +impl FlowTree : tree::WriteMethods<@FlowContext> { + fn add_child(parent: @FlowContext, child: @FlowContext) { + assert !box::ptr_eq(parent, child); + tree::add_child(self, parent, child) + } + + fn with_tree_fields(&&b: @FlowContext, f: fn(tree::Tree<@FlowContext>) -> R) -> R { + f(b.tree) + } +} + +impl @FlowContext { + fn bubble_widths() { match self.kind { - BlockBox => self.reflow_block(), - InlineBox => self.reflow_inline(), - IntrinsicBox(size) => self.reflow_intrinsic(*size), - TextBoxKind(subbox) => self.reflow_text(subbox) + BlockFlow(*) => self.bubble_widths_block(), + InlineFlow(*) => self.bubble_widths_inline(), + RootFlow(*) => self.bubble_widths_root(), + _ => fail fmt!("Tried to bubble_widths of flow: %?", self.kind) } } - #[doc="Dumps the box tree, for debugging, with indentation."] - fn dump_indent(indent: uint) { - let mut s = ~""; - for uint::range(0u, indent) |_i| { - s += ~" "; - } - - s += #fmt("%?", self.kind); - #debug["%s", s]; - - for BTree.each_child(self) |kid| { - kid.dump_indent(indent + 1u) + fn assign_widths() { + match self.kind { + BlockFlow(*) => self.assign_widths_block(), + InlineFlow(*) => self.assign_widths_inline(), + RootFlow(*) => self.assign_widths_root(), + _ => fail fmt!("Tried to assign_widths of flow: %?", self.kind) } } -} -#[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 => box.bounds.size.width = available_width, - InlineBox | 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=" - 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."] - fn reflow_intrinsic(size: Size2D) { - self.bounds.size = copy size; - - #debug["reflow_intrinsic size=%?", copy self.bounds]; - } - - #[doc="Dumps the box tree, for debugging."] - fn dump() { - self.dump_indent(0u); + fn assign_height() { + match self.kind { + BlockFlow(*) => self.assign_height_block(), + InlineFlow(*) => self.assign_height_inline(), + RootFlow(*) => self.assign_height_root(), + _ => fail fmt!("Tried to assign_height of flow: %?", self.kind) + } } } // Debugging -trait PrivateNodeMethods{ +trait DebugMethods { + fn dump(); fn dump_indent(ident: uint); + fn debug_str() -> ~str; } -impl Node : PrivateNodeMethods { - #[doc="Dumps the node tree, for debugging, with indentation."] +impl @FlowContext : DebugMethods { + fn dump() { + self.dump_indent(0u); + } + + /** Dumps the flow tree, for debugging, with indentation. */ + fn dump_indent(indent: uint) { + let mut s = ~"|"; + for uint::range(0u, indent) |_i| { + s += ~"---- "; + } + + s += self.debug_str(); + debug!("%s", s); + + for FlowTree.each_child(self) |child| { + child.dump_indent(indent + 1u) + } + } + + /* TODO: we need a string builder. This is horribly inefficient */ + fn debug_str() -> ~str { + let repr = match self.kind { + InlineFlow(d) => { + let mut s = d.boxes.foldl(~"InlineFlow(children=", |s, box| { + fmt!("%s %?", s, box.id) + }); + s += ~")"; s + }, + BlockFlow(d) => { + match d.box { + Some(b) => fmt!("BlockFlow(box=b%?)", d.box.get().id), + None => ~"BlockFlow", + } + }, + _ => fmt!("%?", self.kind) + }; + + fmt!("c%? %?", self.id, repr) + } +} + +impl Node : DebugMethods { + /* Dumps the subtree rooted at this node, for debugging. */ + fn dump() { + self.dump_indent(0u); + } + /* Dumps the node tree, for debugging, with indentation. */ fn dump_indent(indent: uint) { let mut s = ~""; for uint::range(0u, indent) |_i| { s += ~" "; } - s += #fmt("%?", self.read(|n| copy n.kind )); - #debug["%s", s]; + s += self.debug_str(); + debug!("%s", s); - for NTree.each_child(self) |kid| { + for NodeTree.each_child(self) |kid| { kid.dump_indent(indent + 1u) } } + + fn debug_str() -> ~str { + fmt!("%?", self.read(|n| copy n.kind )) + } } -trait NodeMethods { - fn dump(); -} - -impl Node : NodeMethods { - #[doc="Dumps the subtree rooted at this node, for debugging."] +impl @Box : DebugMethods { fn dump() { self.dump_indent(0u); } + + /* Dumps the node tree, for debugging, with indentation. */ + fn dump_indent(indent: uint) { + let mut s = ~""; + for uint::range(0u, indent) |_i| { + s += ~" "; + } + + s += self.debug_str(); + debug!("%s", s); + + for BoxTree.each_child(self) |kid| { + kid.dump_indent(indent + 1u) + } + } + + fn debug_str() -> ~str { + let repr = match self.kind { + GenericBox(*) => ~"GenericBox", + ImageBox(*) => ~"ImageBox", + TextBox(d) => { + let mut s = d.runs.foldl(~"TextBox(runs=", |s, run| { + fmt!("%s \"%s\"", s, run.text) + }); + s += ~")"; s + } + }; + + fmt!("box b%?: %?", self.id, repr) + } } #[cfg(test)] @@ -360,49 +448,15 @@ mod test { fn flat_bounds(root: @Box) -> ~[Rect] { let mut r = ~[]; - for tree::each_child(BTree, root) |c| { + for tree::each_child(BoxTree, root) |c| { push_all(r, flat_bounds(c)); } - push(r, copy root.bounds); + push(r, copy root.data.position); return r; } - #[test] - #[ignore(reason = "busted")] - fn do_layout() { - let s = Scope(); - - fn mk_img(size: Size2D) -> ~ElementKind { - ~HTMLImageElement({mut size: size}) - } - - let n0 = s.new_node(Element(ElementData(~"img", mk_img(Size2D(au(10),au(10)))))); - let n1 = s.new_node(Element(ElementData(~"img", mk_img(Size2D(au(10),au(10)))))); - let n2 = s.new_node(Element(ElementData(~"img", mk_img(Size2D(au(10),au(20)))))); - let n3 = s.new_node(Element(ElementData(~"div", ~HTMLDivElement))); - - tree::add_child(s, n3, n0); - tree::add_child(s, n3, n1); - tree::add_child(s, n3, n2); - - let b0 = n0.construct_boxes().get(); - let b1 = n1.construct_boxes().get(); - let b2 = n2.construct_boxes().get(); - let b3 = n3.construct_boxes().get(); - - tree::add_child(BTree, b3, b0); - tree::add_child(BTree, b3, b1); - tree::add_child(BTree, b3, b2); - - 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 - geometry::box(au(0), au(10), au(10), au(15)), // n1 - geometry::box(au(0), au(25), au(10), au(20)), // n2 - geometry::box(au(0), au(0), au(100), au(45))]; // n3 - } + // TODO: redo tests here, but probably is part of box_builder.rs } diff --git a/src/servo/layout/block.rs b/src/servo/layout/block.rs index 39fff2f1418..ae222ef6571 100644 --- a/src/servo/layout/block.rs +++ b/src/servo/layout/block.rs @@ -1,57 +1,149 @@ -#[doc="Block layout."] - +use au = gfx::geometry; use css::values::*; use geom::point::Point2D; use geom::size::Size2D; -use gfx::geometry::{px_to_au, au}; +use gfx::geometry::au; +use layout::base::{Box, FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow}; use util::tree; -use base::{Box, BlockBox, BTree}; -trait BlockLayoutMethods { - fn reflow_block(); +struct BlockFlowData { + mut box: Option<@Box> } -#[doc="The public block layout methods."] -impl @Box : BlockLayoutMethods { - #[doc="The main reflow routine for block layout."] - fn reflow_block() { - assert self.kind == BlockBox; - - #debug["starting reflow block"]; - - // Root here is the root of the reflow, not necessarily the doc as - // a whole. - // - // This routine: - // - generates root.bounds.size - // - generates root.bounds.origin 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| { - // FIXME subtract borders, margins, etc - c.bounds.origin = Point2D(au(0), au(current_height)); - current_height += *c.bounds.size.height; - } - - let height = match self.appearance.height { - BoxLength(Px(p)) => px_to_au(p.to_int()), - BoxAuto => au(current_height), - _ => fail ~"inhereit_height failed, height is neither a Px or auto" - }; - - // FIXME: Width is wrong in the calculation below. - let width = match self.appearance.width { - BoxLength(Px(p)) => px_to_au(p.to_int()), - BoxAuto => self.bounds.size.width, // Do nothing here, width was set by top-down pass - _ => fail ~"inhereit_width failed, width is neither a Px or auto" - }; - - self.bounds.size = Size2D(width, height); - - #debug["reflow_block size=%?", copy self.bounds]; +fn BlockFlowData() -> BlockFlowData { + BlockFlowData { + box: None } } +trait BlockLayout { + pure fn starts_block_flow() -> bool; + pure fn access_block(fn(&&BlockFlowData) -> T) -> T; + pure fn with_block_box(fn(&&@Box) -> ()) -> (); + + fn bubble_widths_block(); + fn assign_widths_block(); + fn assign_height_block(); +} + +impl @FlowContext : BlockLayout { + + pure fn starts_block_flow() -> bool { + match self.kind { + RootFlow(*) | BlockFlow(*) | InlineBlockFlow(*) => true, + _ => false + } + } + + pure fn access_block(cb:fn(&&BlockFlowData) -> T) -> T { + match self.kind { + BlockFlow(d) => cb(d), + _ => fail fmt!("Tried to access() data of BlockFlow, but this is a %?", self.kind) + } + } + + /* Get the current flow's corresponding block box, if it exists, and do something with it. + This works on both BlockFlow and RootFlow, since they are mostly the same. */ + pure fn with_block_box(cb:fn(&&@Box) -> ()) -> () { + match self.kind { + BlockFlow(*) => { + do self.access_block |d| { + let mut box = d.box; + box.iter(cb) + } + }, + RootFlow(*) => { + do self.access_root |d| { + let mut box = d.box; + box.iter(cb) + } + }, + _ => fail fmt!("Tried to do something with_block_box(), but this is a %?", self.kind) + } + } + + /* Recursively (bottom-up) determine the context's preferred and + minimum widths. When called on this context, all child contexts + have had their min/pref widths set. This function must decide + min/pref widths based on child context widths and dimensions of + any boxes it is responsible for flowing. */ + + /* TODO: floats */ + /* TODO: absolute contexts */ + /* TODO: inline-blocks */ + fn bubble_widths_block() { + assert self.starts_block_flow(); + + let mut min_width = au(0); + let mut pref_width = au(0); + + /* find max width from child block contexts */ + for FlowTree.each_child(self) |child_ctx| { + assert child_ctx.starts_block_flow() || child_ctx.starts_inline_flow(); + + min_width = au::max(min_width, child_ctx.data.min_width); + pref_width = au::max(pref_width, child_ctx.data.pref_width); + } + + /* if not an anonymous block context, add in block box's widths. + these widths will not include child elements, just padding etc. */ + do self.with_block_box |box| { + min_width += box.get_min_width(); + pref_width += box.get_pref_width(); + } + + self.data.min_width = min_width; + self.data.pref_width = pref_width; + } + + /* Recursively (top-down) determines the actual width of child + contexts and boxes. When called on this context, the context has + had its width set by the parent context. + + Dual boxes consume some width first, and the remainder is assigned to + all child (block) contexts. */ + + fn assign_widths_block() { + assert self.starts_block_flow(); + + let mut remaining_width = self.data.position.size.width; + let mut right_used = au(0); + let mut left_used = au(0); + + /* Let the box consume some width. It will return the amount remaining + for its children. */ + do self.with_block_box |box| { + box.data.position.size.width = remaining_width; + let (left_used, right_used) = box.get_used_width(); + remaining_width -= (left_used + right_used); + } + + for FlowTree.each_child(self) |child_ctx| { + assert child_ctx.starts_block_flow() || child_ctx.starts_inline_flow(); + child_ctx.data.position.origin.x = left_used; + child_ctx.data.position.size.width = remaining_width; + } + } + + fn assign_height_block() { + assert self.starts_block_flow(); + + let mut cur_y = au(0); + + for FlowTree.each_child(self) |child_ctx| { + child_ctx.data.position.origin.y = cur_y; + cur_y += child_ctx.data.position.size.height; + } + + self.data.position.size.height = cur_y; + + let used_top = au(0); + let used_bot = au(0); + + do self.with_block_box |box| { + box.data.position.origin.y = au(0); + box.data.position.size.height = cur_y; + let (used_top, used_bot) = box.get_used_height(); + } + } +} diff --git a/src/servo/layout/box_builder.rs b/src/servo/layout/box_builder.rs index 14399e39382..a8b34a55b92 100644 --- a/src/servo/layout/box_builder.rs +++ b/src/servo/layout/box_builder.rs @@ -1,205 +1,195 @@ -#[doc="Creates CSS boxes from a DOM."] - -use css::values::{CSSDisplay, DisplayBlock, DisplayInline, DisplayNone, Specified}; -use dom::base::{ElementData, HTMLDivElement, HTMLImageElement, Element, Text, Node, Doctype, Comment}; -use gfx::geometry::zero_size_au; -use layout::base::{Appearance, BTree, BlockBox, Box, BoxKind, InlineBox, IntrinsicBox, NTree}; -use layout::base::{TextBoxKind}; -use layout::text::TextBox; -use util::tree; +/** Creates CSS boxes from a DOM. */ +use au = gfx::geometry; +use core::dvec::DVec; +use css::values::{CSSDisplay, DisplayBlock, DisplayInline, DisplayInlineBlock, DisplayNone}; +use css::values::{Inherit, Initial, Specified}; +use dom::base::{ElementData, HTMLDivElement, HTMLImageElement}; +use dom::base::{Element, Text, Node, Doctype, Comment, NodeTree}; +use layout::base::{Box, BoxData, GenericBox, ImageBox, TextBox, BoxTree}; +use layout::base::{FlowContext, FlowContextData, BlockFlow, InlineFlow, InlineBlockFlow, RootFlow, FlowTree}; +use layout::block::BlockFlowData; +use layout::inline::InlineFlowData; +use layout::root::RootFlowData; +use layout::text::TextBoxData; use option::is_none; +use util::tree; +use servo_text::text_run::TextRun; +use servo_text::font_library::FontLibrary; -export box_builder_methods; +export LayoutTreeBuilder; -enum ctxt = { - // The parent node that we're scanning. - parent_node: Node, - // The parent box that these boxes will be added to. - parent_box: @Box, - - // - // The current anonymous box that we're currently appending inline nodes to. - // - // See CSS2 9.2.1.1. - // - - mut anon_box: Option<@Box> -}; - -fn create_context(parent_node: Node, parent_box: @Box) -> ctxt { - return ctxt({ - parent_node: parent_node, - parent_box: parent_box, - mut anon_box: None - }); +struct LayoutTreeBuilder { + mut root_box: Option<@Box>, + mut root_ctx: Option<@FlowContext>, + mut next_bid: int, + mut next_cid: int } -impl ctxt { - #[doc=" - Constructs boxes for the parent's children, when the parent's 'display' attribute is 'block'. - "] - fn construct_boxes_for_block_children() { - for NTree.each_child(self.parent_node) |kid| { - - // Create boxes for the child. Get its primary box. - let kid_box = kid.construct_boxes(); - if (kid_box.is_none()) { - loop - } - - // Determine the child's display. - let disp = kid.get_specified_style().display_type; - if disp != Specified(DisplayInline) { - self.finish_anonymous_box_if_necessary(); - } - - // Add the child's box to the current enclosing box or the current anonymous box. - match kid.get_specified_style().display_type { - Specified(DisplayBlock) => BTree.add_child(self.parent_box, kid_box.get()), - Specified(DisplayInline) => { - let anon_box = match self.anon_box { - None => { - // - // The anonymous box inherits the attributes of its parents for now, so - // that properties of intrinsic boxes are not spread to their parenting - // anonymous box. - // - // TODO: check what CSS actually specifies - // - - let b = @Box(self.parent_node, InlineBox); - self.anon_box = Some(b); - b - } - Some(b) => b - }; - BTree.add_child(anon_box, kid_box.get()); - } - Specified(DisplayNone) => { - // Nothing to do. - } - _ => { //hack for now - } - } - } - } - - #[doc=" - Constructs boxes for the parent's children, when the parent's 'display' - attribute is 'inline'. - "] - fn construct_boxes_for_inline_children() { - for NTree.each_child(self.parent_node) |kid| { - - // Construct boxes for the child. Get its primary box. - let kid_box = kid.construct_boxes(); - - // Determine the child's display. - let disp = kid.get_specified_style().display_type; - if disp != Specified(DisplayInline) { - // TODO - } - - // Add the child's box to the current enclosing box. - match kid.get_specified_style().display_type { - Specified(DisplayBlock) => { - // TODO - #warn("TODO: non-inline display found inside inline box"); - BTree.add_child(self.parent_box, kid_box.get()); - } - Specified(DisplayInline) => { - BTree.add_child(self.parent_box, kid_box.get()); - } - Specified(DisplayNone) => { - // Nothing to do. - } - _ => { //hack for now - } - } - } - } - - #[doc="Constructs boxes for the parent's children."] - fn construct_boxes_for_children() { - #debug("parent node:"); - self.parent_node.dump(); - - match self.parent_node.get_specified_style().display_type { - Specified(DisplayBlock) => self.construct_boxes_for_block_children(), - Specified(DisplayInline) => self.construct_boxes_for_inline_children(), - Specified(DisplayNone) => { /* Nothing to do. */ } - _ => { //hack for now - } - } - - self.finish_anonymous_box_if_necessary(); - assert is_none(self.anon_box); - } - - #[doc=" - Flushes the anonymous box we're creating if it exists. This appends the - anonymous box to the block. - "] - fn finish_anonymous_box_if_necessary() { - match copy self.anon_box { - None => { /* Nothing to do. */ } - Some(b) => BTree.add_child(self.parent_box, b) - } - self.anon_box = None; +fn LayoutTreeBuilder() -> LayoutTreeBuilder { + LayoutTreeBuilder { + root_box: None, + root_ctx: None, + next_bid: -1, + next_cid: -1 } } -trait PrivBoxBuilder { - fn determine_box_kind() -> Option; -} +impl LayoutTreeBuilder { + /* Debug-only ids */ + fn next_box_id() -> int { self.next_bid += 1; self.next_bid } + fn next_ctx_id() -> int { self.next_cid += 1; self.next_cid } -impl Node : PrivBoxBuilder { - #[doc=" - Determines the kind of box that this node needs. Also, for images, computes the intrinsic - size. - "] - fn determine_box_kind() -> Option { - match self.read(|n| copy n.kind) { - ~Text(string) => Some(TextBoxKind(@TextBox(copy string))), - ~Element(element) => { - match (copy *element.kind, self.get_specified_style().display_type) { - (HTMLImageElement({size}), _) => Some(IntrinsicBox(@size)), - (_, Specified(DisplayBlock)) => Some(BlockBox), - (_, Specified(DisplayInline)) => Some(InlineBox), - (_, Specified(DisplayNone)) => None, - (_, Specified(_)) => Some(InlineBox), - (_, _) => { - fail ~"The specified display style should be a default instead of none" - } + /** Creates necessary box(es) and flow context(s) for the current DOM node, + and recurses on its children. */ + fn construct_recursively(cur_node: Node, parent_ctx: @FlowContext, parent_box: @Box) { + let style = cur_node.style(); + + // DEBUG + let n_str = fmt!("%?", cur_node.read(|n| copy n.kind )); + debug!("Considering node: %?", n_str); + + // TODO: handle interactions with 'float', 'position' (CSS 2.1, Section 9.7) + let display = match style.display_type { + Specified(v) => match v { + // tree ends here if 'display: none' + DisplayNone => return, + _ => v + }, + Inherit | Initial => DisplayInline // TODO: fail instead once resolve works + //fail ~"Node should have resolved value for 'display', but was initial or inherit" + }; + + // first, create the proper box kind, based on node characteristics + let box_data = match cur_node.create_box_data(display) { + None => return, + Some(data) => data + }; + + // then, figure out its proper context, possibly reorganizing. + let next_ctx: @FlowContext = match box_data { + /* Text box is always an inline flow. create implicit inline + flow ctx if we aren't inside one already. */ + TextBox(*) => { + if (parent_ctx.starts_inline_flow()) { + parent_ctx + } else { + self.make_ctx(InlineFlow(InlineFlowData()), tree::empty()) } }, - ~Doctype(*) - | ~Comment(*) => None + ImageBox(*) | GenericBox => { + match display { + DisplayInline | DisplayInlineBlock => { + /* if inline, try to put into inline context, + making a new one if necessary */ + if (parent_ctx.starts_inline_flow()) { + parent_ctx + } else { + self.make_ctx(InlineFlow(InlineFlowData()), tree::empty()) + } + }, + /* block boxes always create a new context */ + DisplayBlock => { + self.make_ctx(BlockFlow(BlockFlowData()), tree::empty()) + }, + _ => fail fmt!("unsupported display type in box generation: %?", display) + } + } + }; + + // make box, add box to any context-specific list. + let mut new_box = self.make_box(cur_node, parent_ctx, box_data); + debug!("Assign ^box to flow: %?", next_ctx.debug_str()); + + match next_ctx.kind { + InlineFlow(d) => { d.boxes.push(new_box) } + BlockFlow(d) => { d.box = Some(new_box) } + _ => {} // TODO: float lists, etc. + }; + + // connect the box to its parent box + debug!("Adding child box b%? of b%?", parent_box.id, new_box.id); + BoxTree.add_child(parent_box, new_box); + + if (!next_ctx.eq(parent_ctx)) { + debug!("Adding child flow f%? of f%?", parent_ctx.id, next_ctx.id); + FlowTree.add_child(parent_ctx, next_ctx); } + // recurse + do NodeTree.each_child(cur_node) |child_node| { + self.construct_recursively(child_node, next_ctx, new_box); true + } + + // Fixup any irregularities, such as split inlines (CSS 2.1 Section 9.2.1.1) + if (next_ctx.starts_inline_flow()) { + let mut found_child_inline = false; + let mut found_child_block = false; + + do FlowTree.each_child(next_ctx) |child_ctx| { + match child_ctx.kind { + InlineFlow(*) | InlineBlockFlow => found_child_inline = true, + BlockFlow(*) => found_child_block = true, + _ => {} + }; true + } + + if found_child_block && found_child_inline { + self.fixup_split_inline(next_ctx) + } + } + } + + fn fixup_split_inline(foo: @FlowContext) { + // TODO: finish me. + fail ~"TODO: handle case where an inline is split by a block" + } + + /** entry point for box creation. Should only be + called on root DOM element. */ + fn construct_trees(root: Node) -> Result<@Box, ()> { + self.root_ctx = Some(self.make_ctx(RootFlow(RootFlowData()), tree::empty())); + self.root_box = Some(self.make_box(root, self.root_ctx.get(), GenericBox)); + + self.construct_recursively(root, self.root_ctx.get(), self.root_box.get()); + return Ok(self.root_box.get()) + } + + fn make_ctx(kind : FlowContextData, tree: tree::Tree<@FlowContext>) -> @FlowContext { + let ret = @FlowContext(self.next_ctx_id(), kind, tree); + debug!("Created context: %s", ret.debug_str()); + ret + } + + fn make_box(node : Node, ctx: @FlowContext, data: BoxData) -> @Box { + let ret = @Box(self.next_box_id(), node, ctx, data); + debug!("Created box: %s", ret.debug_str()); + ret } } -trait BoxBuilder { - fn construct_boxes() -> Option<@Box>; +trait PrivateBuilderMethods { + fn create_box_data(display: CSSDisplay) -> Option; } -impl Node : BoxBuilder { - #[doc="Creates boxes for this node. This is the entry point."] - fn construct_boxes() -> Option<@Box> { - match self.determine_box_kind() { - None => None, - Some(kind) => { - let my_box = @Box(self, kind); - match kind { - BlockBox | InlineBox => { - let cx = create_context(self, my_box); - cx.construct_boxes_for_children(); - } - _ => { - // Nothing to do. +impl Node : PrivateBuilderMethods { + fn create_box_data(display: CSSDisplay) -> Option { + do self.read |node| { + match node.kind { + ~Doctype(*) | ~Comment(*) => None, + ~Text(string) => { + // TODO: clean this up. Fonts should not be created here. + let flib = FontLibrary(); + let font = flib.get_test_font(); + let run = TextRun(font, string); + Some(TextBox(TextBoxData(copy string, ~[move run]))) + } + ~Element(element) => { + match (element.kind, display) { + (~HTMLImageElement({size}), _) => Some(ImageBox(size)), +// (_, Specified(_)) => Some(GenericBox), + (_, _) => Some(GenericBox) // TODO: replace this with the commented lines +// (_, _) => fail ~"Can't create box for Node with non-specified 'display' type" } } - Some(my_box) } } } diff --git a/src/servo/layout/display_list_builder.rs b/src/servo/layout/display_list_builder.rs index 6ab83900dea..ee4f068f0db 100644 --- a/src/servo/layout/display_list_builder.rs +++ b/src/servo/layout/display_list_builder.rs @@ -1,18 +1,21 @@ export build_display_list; +use au = gfx::geometry; +use base::Box; use css::values::{BgColor, BgTransparent, Specified}; -use base::{Box, BTree, ImageHolder, TextBoxKind}; use dl = gfx::display_list; use dom::base::{Text, NodeScope}; use dom::rcu::Scope; +use dvec::DVec; use either::{Left, Right}; use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; -use gfx::geometry::{au, au_to_px, box, px_to_au}; -use util::tree; +use gfx::geometry::au; +use layout::text::TextBoxData; +use layout::base::{TextBox, BoxTree}; use servo_text::text_run::TextRun; -use dvec::DVec; +use util::tree; use vec::push; #[doc = " @@ -39,13 +42,13 @@ Builds a display list for a box and all its children. "] fn build_display_list_from_origin(list: dl::DisplayList, box: @Box, origin: Point2D) { let box_origin = Point2D( - px_to_au(au_to_px(origin.x) + au_to_px(box.bounds.origin.x)), - px_to_au(au_to_px(origin.y) + au_to_px(box.bounds.origin.y))); - #debug("Handed origin %?, box has bounds %?, starting with origin %?", origin, copy box.bounds, box_origin); + au::from_px(au::to_px(origin.x) + au::to_px(box.data.position.origin.x)), + au::from_px(au::to_px(origin.y) + au::to_px(box.data.position.origin.y))); + #debug("Handed origin %?, box has bounds %?, starting with origin %?", origin, box.data.position.size, box_origin); box_to_display_items(list, box, box_origin); - for BTree.each_child(box) |c| { + for BoxTree.each_child(box) |c| { #debug("Recursively building display list with origin %?", box_origin); build_display_list_from_origin(list, c, box_origin); } @@ -80,23 +83,23 @@ fn box_to_display_items(list: dl::DisplayList, box: @Box, origin: Point2D) { #debug("request to display a box from origin %?", origin); - let bounds : Rect = Rect(origin, copy box.bounds.size); + let bounds : Rect = Rect(origin, copy box.data.position.size); match box.kind { - TextBoxKind(subbox) => { - let runs = &mut subbox.runs; + TextBox(d) => { + let mut runs = d.runs; list.push(~dl::SolidColor(bounds, 255u8, 255u8, 255u8)); let mut bounds = bounds; for uint::range(0, runs.len()) |i| { bounds.size.height = runs[i].size().height; - let glyph_run = text_run_to_dl_glyph_run(&mut runs[i]); + let glyph_run = text_run_to_dl_glyph_run(& runs[i]); list.push(~dl::Glyphs(bounds, glyph_run)); bounds.origin.y += bounds.size.height; } return; - pure fn text_run_to_dl_glyph_run(text_run: &mut TextRun) -> + pure fn text_run_to_dl_glyph_run(text_run: &TextRun) -> dl::GlyphRun { dl::GlyphRun { glyphs: copy text_run.glyphs @@ -109,14 +112,14 @@ fn box_to_display_items(list: dl::DisplayList, box: @Box, origin: Point2D) { }; // Check if there is a background image, if not set the background color. - let image = box.appearance.get_image(); + let image = box.get_image(); if image.is_some() { list.push(~dl::Image(bounds, option::unwrap(image))) } else { // DAC // TODO: shouldn't need to unbox CSSValue by now - let boxed_color = box.node.get_specified_style().background_color; + let boxed_color = box.node.style().background_color; let color = match boxed_color { Specified(BgColor(c)) => c, Specified(BgTransparent) | _ => util::color::rgba(0,0,0,0.0) @@ -130,18 +133,16 @@ fn box_to_display_items(list: dl::DisplayList, box: @Box, origin: Point2D) { fn should_convert_text_boxes_to_solid_color_background_items() { #[test]; + use layout::box_builder::LayoutTreeBuilder; + let s = Scope(); let n = s.new_node(Text(~"firecracker")); - let b = n.construct_boxes().get(); + let builder = LayoutTreeBuilder(); + let b = builder.construct_trees(n).get(); - let subbox = match b.kind { - TextBoxKind(subbox) => subbox, - _ => fail - }; - - b.reflow_text(subbox); + b.reflow_text(); let list = DVec(); - box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0))); + box_to_display_items(list, b, Point2D(au::from_px(0), au::from_px(0))); do list.borrow |l| { match l[0].data { @@ -153,19 +154,16 @@ fn should_convert_text_boxes_to_solid_color_background_items() { fn should_convert_text_boxes_to_text_items() { #[test]; + use layout::box_builder::LayoutTreeBuilder; let s = Scope(); let n = s.new_node(Text(~"firecracker")); - let b = n.construct_boxes().get(); + let builder = LayoutTreeBuilder(); + let b = builder.construct_trees(n).get(); - let subbox = match b.kind { - TextBoxKind(subbox) => { subbox }, - _ => fail - }; - - b.reflow_text(subbox); + b.reflow_text(); let list = DVec(); - box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0))); + box_to_display_items(list, b, Point2D(au::from_px(0), au::from_px(0))); do list.borrow |l| { match l[1].data { @@ -178,23 +176,20 @@ fn should_convert_text_boxes_to_text_items() { fn should_calculate_the_bounds_of_the_text_box_background_color() { #[test]; #[ignore(cfg(target_os = "macos"))]; + use layout::box_builder::LayoutTreeBuilder; let s = Scope(); let n = s.new_node(Text(~"firecracker")); - let b = n.construct_boxes().get(); + let builder = LayoutTreeBuilder(); + let b = builder.construct_trees(n).get(); - let subbox = match b.kind { - TextBoxKind(subbox) => { subbox }, - _ => fail - }; - - b.reflow_text(subbox); + b.reflow_text(); let list = DVec(); - box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0))); + box_to_display_items(list, b, Point2D(au::from_px(0), au::from_px(0))); let expected = Rect( - Point2D(px_to_au(0), px_to_au(0)), - Size2D(px_to_au(84), px_to_au(20)) + Point2D(au::from_px(0), au::from_px(0)), + Size2D(au::from_px(84), au::from_px(20)) ); do list.borrow |l| { assert l[0].bounds == expected } @@ -203,23 +198,20 @@ fn should_calculate_the_bounds_of_the_text_box_background_color() { fn should_calculate_the_bounds_of_the_text_items() { #[test]; #[ignore(reason = "busted")]; + use layout::box_builder::LayoutTreeBuilder; let s = Scope(); let n = s.new_node(Text(~"firecracker")); - let b = n.construct_boxes().get(); + let builder = LayoutTreeBuilder(); + let b = builder.construct_trees(n).get(); - let subbox = match b.kind { - TextBoxKind(subbox) => { subbox }, - _ => fail - }; - - b.reflow_text(subbox); + b.reflow_text(); let list = DVec(); - box_to_display_items(list, b, Point2D(px_to_au(0), px_to_au(0))); + box_to_display_items(list, b, Point2D(au::from_px(0), au::from_px(0))); let expected = Rect( - Point2D(px_to_au(0), px_to_au(0)), - Size2D(px_to_au(84), px_to_au(20)) + Point2D(au::from_px(0), au::from_px(0)), + Size2D(au::from_px(84), au::from_px(20)) ); do list.borrow |l| { assert l[1].bounds == expected; } diff --git a/src/servo/layout/inline.rs b/src/servo/layout/inline.rs index c57a5d6a3d9..b0b7cfe1a1d 100644 --- a/src/servo/layout/inline.rs +++ b/src/servo/layout/inline.rs @@ -1,54 +1,125 @@ -#[doc="Inline layout."] - -use base::{Box, InlineBox, BTree}; +use au = gfx::geometry; +use base::Box; +use core::dvec::DVec; use css::values::{BoxAuto, BoxLength, Px}; use dom::rcu; use geom::point::Point2D; use geom::size::Size2D; -use gfx::geometry::{au, px_to_au}; +use gfx::geometry::au; +use layout::base::{FlowContext, InlineFlow, BoxTree, ImageBox, TextBox, GenericBox}; use num::Num; use util::tree; -trait InlineLayout { - fn reflow_inline(); +struct InlineFlowData { + boxes: ~DVec<@Box> } -#[doc="The main reflow routine for inline layout."] -impl @Box : InlineLayout { - 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; - 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)); - x += *kid.bounds.size.width; - current_height = i32::max(current_height, *kid.bounds.size.height); - } - - let height = match self.appearance.height { - BoxLength(Px(p)) => px_to_au(p.to_int()), - BoxAuto => au(current_height), - _ => fail ~"inhereit_height failed, height is neither a Px or auto" - }; - - let width = match self.appearance.width { - BoxLength(Px(p)) => px_to_au(p.to_int()), - BoxAuto => au(i32::max(x, *self.bounds.size.width)), - _ => fail ~"inhereit_width failed, width is neither a Px or auto" - }; - - // The maximum available width should have been set in the top-down pass - self.bounds.size = Size2D(width, height); - - #debug["reflow_inline size=%?", copy self.bounds]; +fn InlineFlowData() -> InlineFlowData { + InlineFlowData { + boxes: ~DVec() } } +trait InlineLayout { + pure fn starts_inline_flow() -> bool; + + pure fn access_inline(fn(&&InlineFlowData) -> T) -> T; + fn bubble_widths_inline(); + fn assign_widths_inline(); + fn assign_height_inline(); +} + +impl @FlowContext : InlineLayout { + pure fn starts_inline_flow() -> bool { match self.kind { InlineFlow(*) => true, _ => false } } + + pure fn access_inline(cb: fn(&&InlineFlowData) -> T) -> T { + match self.kind { + InlineFlow(d) => cb(d), + _ => fail fmt!("Tried to access() data of InlineFlow, but this is a %?", self.kind) + } + } + + fn bubble_widths_inline() { + assert self.starts_inline_flow(); + + let mut min_width = au(0); + let mut pref_width = au(0); + + /* TODO: implement a "line sizes" API. Each inline element + should report its longest possible chunk (i.e., text run) and + shortest chunk (i.e., smallest word or hyphenatable segment). + + Until this exists, pretend that the text is indivisible, just + like a replaced element. */ + + do self.access_inline |d| { + for d.boxes.each |box| { + min_width = au::max(min_width, box.get_min_width()); + pref_width = au::max(pref_width, box.get_pref_width()); + } + } + + self.data.min_width = min_width; + self.data.pref_width = pref_width; + } + + /* Recursively (top-down) determines the actual width of child + contexts and boxes. When called on this context, the context has + had its width set by the parent context. */ + fn assign_widths_inline() { + assert self.starts_inline_flow(); + + /* Perform inline flow with the available width. */ + //let avail_width = self.data.position.size.width; + + let line_height = au::from_px(20); + //let mut cur_x = au(0); + let mut cur_y = au(0); + + do self.access_inline |d| { + for d.boxes.each |box| { + /* TODO: actually do inline flow. + - Create a working linebox, and successively put boxes + into it, splitting if necessary. + + - Set width and height for each positioned element based on + where its chunks ended up. + + - Save the dvec of this context's lineboxes. */ + + box.data.position.size.width = match box.kind { + ImageBox(sz) => sz.width, + TextBox(d) => d.runs[0].size().width, + // TODO: this should be set to the extents of its children + GenericBox(*) => au(0) + }; + + box.data.position.size.height = match box.kind { + ImageBox(sz) => sz.height, + TextBox(d) => d.runs[0].size().height, + // TODO: this should be set to the extents of its children + GenericBox(*) => au(0) + }; + + box.data.position.origin = Point2D(au(0), cur_y); + cur_y += au::max(line_height, box.data.position.size.height); + } // for boxes.each |box| + } + + self.data.position.size.height = cur_y; + + /* There are no child contexts, so stop here. */ + + // TODO: once there are 'inline-block' elements, this won't be + // true. In that case, perform inline flow, and then set the + // block flow context's width as the width of the + // 'inline-block' box that created this flow. + + } // fn assign_widths_inline + + fn assign_height_inline() { + // Don't need to set box or ctx heights, since that is done + // during inline flowing. + } + +} // @FlowContext : InlineLayout diff --git a/src/servo/layout/layout_task.rs b/src/servo/layout/layout_task.rs index f84840894b6..35d9148f603 100644 --- a/src/servo/layout/layout_task.rs +++ b/src/servo/layout/layout_task.rs @@ -3,22 +3,24 @@ rendered. "]; -use std::arc::ARC; +use au = gfx::geometry; +use content::content_task; +use css::resolve::apply::apply_style; +use css::values::Stylesheet; use display_list_builder::build_display_list; use dom::base::Node; -use css::values::Stylesheet; -use gfx::geometry::px_to_au; -use gfx::render_task; -use render_task::RenderTask; -use layout::base::Box; -use resource::image_cache_task::ImageCacheTask; -use std::net::url::Url; -use css::resolve::apply::apply_style; use dom::event::{Event, ReflowEvent}; -use content::content_task; +use gfx::render_task; +use layout::base::Box; +use layout::box_builder::LayoutTreeBuilder; +use render_task::RenderTask; +use resource::image_cache_task::ImageCacheTask; +use std::arc::ARC; +use std::net::url::Url; -use task::*; +use layout::traverse::*; use comm::*; +use task::*; type LayoutTask = Chan; @@ -38,11 +40,12 @@ fn LayoutTask(render_task: RenderTask, image_cache_task: ImageCacheTask) -> Layo match request.recv() { PingMsg(ping_channel) => ping_channel.send(content_task::PongMsg), ExitMsg => { - #debug("layout: ExitMsg received"); + debug!("layout: ExitMsg received"); break; } BuildMsg(node, styles, doc_url, event_chan) => { - #debug("layout: received layout request for:"); + debug!("layout: received layout request for: %s", doc_url.to_str()); + debug!("layout: parsed Node tree"); node.dump(); do util::time::time(~"layout") { @@ -50,18 +53,29 @@ fn LayoutTask(render_task: RenderTask, image_cache_task: ImageCacheTask) -> Layo node.recompute_style_for_subtree(styles); let root_box: @Box; - match node.construct_boxes() { - None => fail ~"Root node should always exist; did it get 'display: none' somehow?", - Some(root) => root_box = root + let builder = LayoutTreeBuilder(); + match builder.construct_trees(node) { + Ok(root) => root_box = root, + Err(*) => fail ~"Root node should always exist" } - + + debug!("layout: constructed Box tree"); root_box.dump(); - let reflow: fn~() = || event_chan.send(ReflowEvent); + debug!("layout: constructed Flow tree"); + root_box.ctx.dump(); - apply_style(root_box, &doc_url, image_cache_task, reflow); + /* resolve styles (convert relative values) down the box tree */ + let reflow_cb: fn~() = || event_chan.send(ReflowEvent); + apply_style(root_box, &doc_url, image_cache_task, reflow_cb); - root_box.reflow_subtree(px_to_au(800)); + /* perform layout passes over the flow tree */ + let root_flow = root_box.ctx; + do root_flow.traverse_postorder |f| { f.bubble_widths() } + root_flow.data.position.origin = au::zero_point(); + root_flow.data.position.size.width = au::from_px(800); // TODO: window/frame size + do root_flow.traverse_preorder |f| { f.assign_widths() } + do root_flow.traverse_postorder |f| { f.assign_height() } let dlist = build_display_list(root_box); render_task.send(render_task::RenderMsg(dlist)); diff --git a/src/servo/layout/root.rs b/src/servo/layout/root.rs new file mode 100644 index 00000000000..de0a44da691 --- /dev/null +++ b/src/servo/layout/root.rs @@ -0,0 +1,62 @@ +use au = gfx::geometry; +use css::values::*; +use geom::point::Point2D; +use geom::size::Size2D; +use gfx::geometry::au; +use layout::base::{Box, FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow}; +use util::tree; + +struct RootFlowData { + mut box: Option<@Box> +} + +fn RootFlowData() -> RootFlowData { + RootFlowData { + box: None + } +} + +trait RootLayout { + pure fn starts_root_flow() -> bool; + pure fn access_root(fn(&&RootFlowData) -> T) -> T; + + fn bubble_widths_root(); + fn assign_widths_root(); + fn assign_height_root(); +} + +impl @FlowContext : RootLayout { + + pure fn starts_root_flow() -> bool { + match self.kind { + RootFlow(*) => true, + _ => false + } + } + + pure fn access_root(cb:fn(&&RootFlowData) -> T) -> T { + match self.kind { + RootFlow(d) => cb(d), + _ => fail fmt!("Tried to access() data of RootFlow, but this is a %?", self.kind) + } + } + + /* defer to the block algorithm */ + fn bubble_widths_root() { + assert self.starts_root_flow(); + self.bubble_widths_block() + } + + fn assign_widths_root() { + assert self.starts_root_flow(); + + /* TODO: should determine frame width here, not in + LayoutTask. Until then, defer to block. */ + self.assign_widths_block() } + + fn assign_height_root() { + assert self.starts_root_flow(); + + self.assign_height_block(); + } +} diff --git a/src/servo/layout/text.rs b/src/servo/layout/text.rs index 5c7417cf38a..a00d7805d12 100644 --- a/src/servo/layout/text.rs +++ b/src/servo/layout/text.rs @@ -1,32 +1,33 @@ #[doc="Text layout."] +use au = gfx::geometry; use geom::size::Size2D; -use gfx::geometry::{au, px_to_au}; +use gfx::geometry::au; use servo_text::text_run::TextRun; use servo_text::font_library::FontLibrary; -use base::{Box, TextBoxKind}; +use layout::base::{TextBox, Box}; -struct TextBox { +struct TextBoxData { text: ~str, - mut runs: ~[TextRun], + mut runs: ~[TextRun] } -fn TextBox(text: ~str) -> TextBox { - TextBox { +fn TextBoxData(text: ~str, runs: ~[TextRun]) -> TextBoxData { + TextBoxData { text: text, - runs: ~[], + runs: runs } } trait TextLayout { - fn reflow_text(subbox: @TextBox); + fn reflow_text(); } #[doc="The main reflow routine for text layout."] impl @Box : TextLayout { - fn reflow_text(subbox: @TextBox) { - match self.kind { - TextBoxKind(*) => { /* ok */ } + fn reflow_text() { + let d = match self.kind { + TextBox(d) => { d } _ => { fail ~"expected text box in reflow_text!" } }; @@ -35,9 +36,9 @@ impl @Box : TextLayout { let font = flib.get_test_font(); // Do line breaking. - let mut current = TextRun(font, subbox.text); + let mut current = TextRun(font, d.text); let mut lines = dvec::DVec(); - let mut width_left = px_to_au(800); + let mut width_left = au::from_px(800); let mut max_width = au(0); while current.size().width > width_left { @@ -73,8 +74,8 @@ impl @Box : TextLayout { let total_height = au(*current.size().height * line_count); lines.push(move current); - self.bounds.size = Size2D(max_width, total_height); - subbox.runs = dvec::unwrap(lines); + self.data.position.size = Size2D(max_width, total_height); + d.runs = move dvec::unwrap(lines); } } @@ -82,20 +83,18 @@ fn should_calculate_the_size_of_the_text_box() { #[test]; #[ignore(cfg(target_os = "macos"))]; + use au = gfx::geometry; use dom::rcu::{Scope}; use dom::base::{Text, NodeScope}; use util::tree; - use gfx::geometry::px_to_au; + use layout::box_builder::LayoutTreeBuilder; let s = Scope(); let n = s.new_node(Text(~"firecracker")); - let b = n.construct_boxes().get(); + let builder = LayoutTreeBuilder(); + let b = builder.construct_trees(n).get(); - let subbox = match b.kind { - TextBoxKind(subbox) => { subbox }, - _ => fail - }; - b.reflow_text(subbox); - let expected = Size2D(px_to_au(84), px_to_au(20)); - assert b.bounds.size == expected; + b.reflow_text(); + let expected = Size2D(au::from_px(84), au::from_px(20)); + assert b.data.position.size == expected; } diff --git a/src/servo/layout/traverse.rs b/src/servo/layout/traverse.rs new file mode 100644 index 00000000000..bf488300034 --- /dev/null +++ b/src/servo/layout/traverse.rs @@ -0,0 +1,32 @@ +/** Interface for running tree-based traversals over layout boxes and contextsg */ + +use layout::base::{Box, BoxTree}; +use layout::base::{FlowContext, FlowTree}; + +trait BoxTraversals { + fn traverse_preorder(preorder_cb: ~fn(@Box)); +} + +impl @Box : BoxTraversals { + fn traverse_preorder(preorder_cb: ~fn(@Box)) { + preorder_cb(self); + do BoxTree.each_child(self) |child| { child.traverse_preorder(preorder_cb); true } + } +} + +trait FlowContextTraversals { + fn traverse_preorder(preorder_cb: ~fn(@FlowContext)); + fn traverse_postorder(postorder_cb: ~fn(@FlowContext)); +} + +impl @FlowContext : FlowContextTraversals { + fn traverse_preorder(preorder_cb: ~fn(@FlowContext)) { + preorder_cb(self); + do FlowTree.each_child(self) |child| { child.traverse_preorder(preorder_cb); true } + } + + fn traverse_postorder(postorder_cb: ~fn(@FlowContext)) { + do FlowTree.each_child(self) |child| { child.traverse_postorder(postorder_cb); true } + postorder_cb(self); + } +} diff --git a/src/servo/layout/traverse_parallel.rs b/src/servo/layout/traverse_parallel.rs index 5a071b9004b..c7c7271cc78 100644 --- a/src/servo/layout/traverse_parallel.rs +++ b/src/servo/layout/traverse_parallel.rs @@ -1,6 +1,6 @@ -#[doc = "Interface for running tree-based traversals over layout boxes"] -use base::{Box, BTree, NodeMethods}; +#[doc = "Interface for running tree-based traversals over layout boxes"] +use base::{Box, BoxTree}; use intrinsic::TyDesc; export full_traversal; @@ -67,7 +67,7 @@ fn traverse_helper(-root : @Box, returned : T, -top_down : fn~(+T // current task will block until all of it's children return, // so the original owner of the @-box will not exit while the // children are still live. - for BTree.each_child(root) |kid| { + for BoxTree.each_child(root) |kid| { count += 1; // Unwrap the box so we can send it out of this task diff --git a/src/servo/servo.rc b/src/servo/servo.rc index bd5de682d35..8705ac30f7d 100755 --- a/src/servo/servo.rc +++ b/src/servo/servo.rc @@ -58,7 +58,9 @@ mod layout { mod display_list_builder; mod inline; mod layout_task; + mod root; mod text; + mod traverse; mod traverse_parallel; } @@ -73,6 +75,7 @@ mod gfx { mod image { mod base; + mod holder; mod encode { mod tga; } @@ -97,17 +100,17 @@ mod text { export font; export shaper; - mod glyph; - mod text_run; mod font; mod font_library; - mod shaper; + mod glyph; mod native_font { #[cfg(target_os = "macos")] mod quartz_native_font; #[cfg(target_os = "linux")] mod ft_native_font; } + mod shaper; + mod text_run; mod util; } diff --git a/src/servo/text/shaper.rs b/src/servo/text/shaper.rs index 02dd73218ec..3745c81fc6e 100644 --- a/src/servo/text/shaper.rs +++ b/src/servo/text/shaper.rs @@ -2,12 +2,13 @@ extern mod harfbuzz; export shape_text; +use au = gfx::geometry; use libc::types::common::c99::int32_t; use libc::{c_uint, c_int, c_void, c_char}; use font::Font; use glyph::{Glyph, GlyphPos}; use ptr::{null, addr_of, offset}; -use gfx::geometry::{au, px_to_au}; +use gfx::geometry::au; use geom::point::Point2D; use font_library::FontLibrary; @@ -137,10 +138,10 @@ extern fn glyph_h_advance_func(_font: *hb_font_t, } fn hb_glyph_pos_to_servo_glyph_pos(hb_pos: &hb_glyph_position_t) -> GlyphPos { - GlyphPos(Point2D(px_to_au(hb_pos.x_advance as int), - px_to_au(hb_pos.y_advance as int)), - Point2D(px_to_au(hb_pos.x_offset as int), - px_to_au(hb_pos.y_offset as int))) + GlyphPos(Point2D(au::from_px(hb_pos.x_advance as int), + au::from_px(hb_pos.y_advance as int)), + Point2D(au::from_px(hb_pos.x_offset as int), + au::from_px(hb_pos.y_offset as int))) } fn should_get_glyph_indexes() { @@ -162,6 +163,6 @@ fn should_get_glyph_h_advance() { let font = lib.get_test_font(); let glyphs = shape_text(font, ~"firecracker"); let actual = glyphs.map(|g| g.pos.advance.x); - let expected = (~[6, 4, 7, 9, 8, 7, 10, 8, 9, 9, 7]).map(|a| px_to_au(a)); + let expected = (~[6, 4, 7, 9, 8, 7, 10, 8, 9, 9, 7]).map(|a| au::from_px(a)); assert expected == actual; } diff --git a/src/servo/text/text_run.rs b/src/servo/text/text_run.rs index 16c4e6a4e27..c8a847c4090 100644 --- a/src/servo/text/text_run.rs +++ b/src/servo/text/text_run.rs @@ -1,6 +1,7 @@ +use au = gfx::geometry; use geom::point::Point2D; use geom::size::Size2D; -use gfx::geometry::{au, px_to_au}; +use gfx::geometry::au; use libc::{c_void}; use font_library::FontLibrary; use font::Font; @@ -9,7 +10,7 @@ use shaper::shape_text; /// A single, unbroken line of text struct TextRun { - priv text: ~str, + text: ~str, priv glyphs: ~[Glyph], priv size_: Size2D, priv min_break_width_: au, @@ -76,8 +77,8 @@ fn TextRun(font: &Font, +text: ~str) -> TextRun { } fn glyph_run_size(glyphs: &[Glyph]) -> Size2D { - let height = px_to_au(20); - let pen_start_x = px_to_au(0); + let height = au::from_px(20); + let pen_start_x = au::from_px(0); let pen_start_y = height; let pen_start = Point2D(pen_start_x, pen_start_y); let pen_end = glyphs.foldl(pen_start, |cur, glyph| { @@ -137,7 +138,7 @@ fn test_calc_min_break_width1() { let flib = FontLibrary(); let font = flib.get_test_font(); let actual = calc_min_break_width(font, ~"firecracker"); - let expected = px_to_au(84); + let expected = au::from_px(84); assert expected == actual; } @@ -146,7 +147,7 @@ fn test_calc_min_break_width2() { let flib = FontLibrary(); let font = flib.get_test_font(); let actual = calc_min_break_width(font, ~"firecracker yumyum"); - let expected = px_to_au(84); + let expected = au::from_px(84); assert expected == actual; } @@ -155,7 +156,7 @@ fn test_calc_min_break_width3() { let flib = FontLibrary(); let font = flib.get_test_font(); let actual = calc_min_break_width(font, ~"yumyum firecracker"); - let expected = px_to_au(84); + let expected = au::from_px(84); assert expected == actual; } @@ -164,7 +165,7 @@ fn test_calc_min_break_width4() { let flib = FontLibrary(); let font = flib.get_test_font(); let actual = calc_min_break_width(font, ~"yumyum firecracker yumyum"); - let expected = px_to_au(84); + let expected = au::from_px(84); assert expected == actual; } @@ -237,7 +238,7 @@ fn test_split3() { let flib = FontLibrary(); let font = flib.get_test_font(); let run = TextRun(font, ~"firecracker firecracker"); - let break_runs = run.split(font, run.min_break_width() + px_to_au(10)); + let break_runs = run.split(font, run.min_break_width() + au::from_px(10)); assert break_runs.first().text == ~"firecracker"; assert break_runs.second().text == ~"firecracker"; @@ -249,7 +250,7 @@ fn should_calculate_the_total_size() { let flib = FontLibrary(); let font = flib.get_test_font(); let run = TextRun(font, ~"firecracker"); - let expected = Size2D(px_to_au(84), px_to_au(20)); + let expected = Size2D(au::from_px(84), au::from_px(20)); assert run.size() == expected; }