diff --git a/src/rust-geom b/src/rust-geom index e1ec99049c1..01de30b15bf 160000 --- a/src/rust-geom +++ b/src/rust-geom @@ -1 +1 @@ -Subproject commit e1ec99049c193aaedbb603e4ceb675251e5814b3 +Subproject commit 01de30b15bfb3ecdfb921b7571ff98e63c0d4483 diff --git a/src/servo/css/resolve/apply.rs b/src/servo/css/resolve/apply.rs index ca58696a750..0e4ee8a6dde 100644 --- a/src/servo/css/resolve/apply.rs +++ b/src/servo/css/resolve/apply.rs @@ -1,7 +1,7 @@ #[doc="Applies the appropriate CSS style to boxes."] use au = gfx::geometry; -use layout::base::{Box, SpecifiedStyle, BoxTree}; +use layout::base::{RenderBox, SpecifiedStyle, RenderBoxTree}; use layout::context::LayoutContext; use layout::traverse_parallel::top_down_traversal; use image::ImageHolder; @@ -32,12 +32,12 @@ impl CSSValue : ResolveMethods { struct StyleApplicator { - box: @Box, + box: @RenderBox, reflow: fn~(), } // TODO: normalize this into a normal preorder tree traversal function -fn apply_style(layout_ctx: &LayoutContext, box: @Box, reflow: fn~()) { +fn apply_style(layout_ctx: &LayoutContext, box: @RenderBox, reflow: fn~()) { let applicator = StyleApplicator { box: box, reflow: reflow @@ -50,7 +50,7 @@ fn apply_style(layout_ctx: &LayoutContext, box: @Box, reflow: fn~()) { #[doc="A wrapper around a set of functions that can be applied as a top-down traversal of layout boxes."] -fn inheritance_wrapper(layout_ctx: &LayoutContext, box : @Box, reflow: fn~()) { +fn inheritance_wrapper(layout_ctx: &LayoutContext, box : @RenderBox, reflow: fn~()) { let applicator = StyleApplicator { box: box, reflow: reflow @@ -59,12 +59,12 @@ fn inheritance_wrapper(layout_ctx: &LayoutContext, box : @Box, reflow: fn~()) { } /* -fn resolve_fontsize(box : @Box) { +fn resolve_fontsize(box : @RenderBox) { // TODO: complete this return } -fn resolve_height(box : @Box) -> au { +fn resolve_height(box : @RenderBox) -> au { let style = box.node.get_style(); let inherit_val = match box.tree.parent { None => au(0), @@ -81,7 +81,7 @@ fn resolve_height(box : @Box) -> au { } } -fn resolve_width(box : @Box) { +fn resolve_width(box : @RenderBox) { let style = box.node.get_specified_style(); let inherit_val = match box.tree.parent { None => style.height.initial(), @@ -102,7 +102,7 @@ impl StyleApplicator { fn apply_css_style(layout_ctx: &LayoutContext) { let reflow = copy self.reflow; - do BoxTree.each_child(self.box) |child| { + do RenderBoxTree.each_child(self.box) |child| { inheritance_wrapper(layout_ctx, child, reflow); true } } diff --git a/src/servo/dom/base.rs b/src/servo/dom/base.rs index fe712c63ec1..3eb5a5e1a56 100644 --- a/src/servo/dom/base.rs +++ b/src/servo/dom/base.rs @@ -13,7 +13,7 @@ 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 layout::base::RenderBox; use layout::debug::DebugMethods; use ptr::null; use std::arc::ARC; @@ -193,12 +193,12 @@ enum ElementKind { /** 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`. + as well as the primary `RenderBox`. Note that there may be multiple boxes per DOM node. */ enum LayoutData = { mut style: ~SpecifiedStyle, - mut box: Option<@Box> + mut box: Option<@RenderBox> }; type Node = rcu::Handle; diff --git a/src/servo/gfx/display_list.rs b/src/servo/gfx/display_list.rs index a8e03750336..29fa62f8bfa 100644 --- a/src/servo/gfx/display_list.rs +++ b/src/servo/gfx/display_list.rs @@ -9,6 +9,8 @@ use std::arc::{ARC, clone}; use dvec::DVec; use text::glyph::Glyph; +pub use layout::display_list_builder::DisplayListBuilder; + struct DisplayItem { draw: ~fn((&DisplayItem), (&RenderContext)), bounds : Rect, // TODO: whose coordinate system should this use? @@ -88,7 +90,7 @@ trait DisplayListMethods { impl DisplayList : DisplayListMethods { fn draw(ctx: &RenderContext) { for self.each |item| { - debug!["drawing %?", item]; + debug!("drawing %?", item); item.draw(item, ctx); } } diff --git a/src/servo/layout/base.rs b/src/servo/layout/base.rs index d98632f8a3d..98980704dfb 100644 --- a/src/servo/layout/base.rs +++ b/src/servo/layout/base.rs @@ -1,17 +1,21 @@ /* Fundamental layout structures and algorithms. */ +use arc = std::arc; +use arc::ARC; use au = gfx::geometry; +use au::au; use core::dvec::DVec; use core::to_str::ToStr; use core::rand; use css::styles::SpecifiedStyle; -use css::values::{BoxSizing, Length, Px, CSSDisplay}; +use css::values::{BoxSizing, Length, Px, CSSDisplay, Specified, BgColor, BgTransparent}; +use dl = gfx::display_list; use dom::base::{Element, ElementKind, HTMLDivElement, HTMLImageElement}; use dom::base::{Node, NodeData, NodeKind, NodeTree}; use dom::rcu; use geom::rect::Rect; use geom::size::Size2D; -use gfx::geometry::au; +use geom::point::Point2D; use image::{Image, ImageHolder}; use layout::block::BlockFlowData; use layout::context::LayoutContext; @@ -20,14 +24,73 @@ 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}; + +/** Servo's experimental layout system builds a tree of FlowContexts +and RenderBoxes, and figures out positions and display attributes of +tree nodes. Positions are computed in several tree traversals driven +by fundamental data dependencies of inline and block layout. + +Render boxes (`struct RenderBox`) are the leafs of the layout +tree. They cannot position themselves. In general, render boxes do not +have a simple correspondence with CSS boxes as in the specification: + + * Several render boxes may correspond to the same CSS box or DOM + node. For example, a CSS text box broken across two lines is + represented by two render boxes. + + * Some CSS boxes are not created at all, such as some anonymous block + boxes induced by inline boxes with block-level sibling boxes. In + that case, Servo uses an InlineFlow with BlockFlow siblings; the + InlineFlow is block-level, but not a block container. It is + positioned as if it were a block box, but its children are + positioned according to inline flow. + +Fundamental box types include: + + * GenericBox: an empty box that contributes only borders, margins, +padding, backgrounds. It is analogous to a CSS nonreplaced content box. + + * ImageBox: a box that represents a (replaced content) image and its + accompanying borders, shadows, etc. + + * TextBox: a box representing a single run of text with a distinct + style. A TextBox may be split into two or more render boxes across + line breaks. Several TextBoxes may correspond to a single DOM text + node. Split text boxes are implemented by referring to subsets of a + master TextRun object. + + +Flows (`struct FlowContext`) are interior nodes in the layout tree, +and correspond closely to flow contexts in the CSS +specification. Flows are responsible for positioning their child flow +contexts and render boxes. Flows have purpose-specific fields, such as +auxilliary line box structs, out-of-flow child lists, and so on. + +Currently, the important types of flows are: + + * BlockFlow: a flow that establishes a block context. It has several + child flows, each of which are positioned according to block + formatting context rules (as if child flows CSS block boxes). Block + flows also contain a single GenericBox to represent their rendered + borders, padding, etc. (In the future, this render box may be + folded into BlockFlow to save space.) + + * InlineFlow: a flow that establishes an inline context. It has a + flat list of child boxes/flows that are subject to inline layout + and line breaking, and structs to represent line breaks and mapping + to CSS boxes, for the purpose of handling `getClientRects()`. + +*/ + struct FlowLayoutData { + // TODO: min/pref and position are used during disjoint phases of + // layout; maybe combine into a single enum to save space. mut min_width: au, mut pref_width: au, mut position: Rect, @@ -42,7 +105,7 @@ fn FlowLayoutData() -> FlowLayoutData { } /* The type of the formatting context, and data specific to each -context, such as lineboxes or float lists */ +context, such as linebox structures or float lists */ enum FlowContextData { AbsoluteFlow, BlockFlow(BlockFlowData), @@ -54,14 +117,7 @@ enum FlowContextData { } /* 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. -*/ + render boxes within the context. */ struct FlowContext { kind: FlowContextData, data: FlowLayoutData, @@ -85,6 +141,8 @@ impl @FlowContext : cmp::Eq { pure fn ne(&&other: @FlowContext) -> bool { !box::ptr_eq(self, other) } } + +/* Flow context disambiguation methods: the verbose alternative to virtual methods */ impl @FlowContext { fn bubble_widths(ctx: &LayoutContext) { match self.kind { @@ -112,6 +170,16 @@ impl @FlowContext { _ => fail fmt!("Tried to assign_height of flow: %?", self.kind) } } + + fn build_display_list_recurse(builder: &dl::DisplayListBuilder, dirty: &Rect, + offset: &Point2D, list: &dl::DisplayList) { + match self.kind { + RootFlow(*) => self.build_display_list_root(builder, dirty, offset, list), + BlockFlow(*) => self.build_display_list_block(builder, dirty, offset, list), + InlineFlow(*) => self.build_display_list_inline(builder, dirty, offset, list), + _ => fail fmt!("Tried to build_display_list_recurse of flow: %?", self.kind) + } + } } /* The tree holding FlowContexts */ @@ -166,9 +234,9 @@ enum BoxData { TextBox(TextBoxData) } -struct Box { +struct RenderBox { /* references to children, parent */ - tree : tree::Tree<@Box>, + tree : tree::Tree<@RenderBox>, /* originating DOM node */ node : Node, /* reference to containing flow context, which this box @@ -182,8 +250,8 @@ struct Box { mut id: int } -fn Box(id: int, node: Node, ctx: @FlowContext, kind: BoxData) -> Box { - Box { +fn RenderBox(id: int, node: Node, ctx: @FlowContext, kind: BoxData) -> RenderBox { + RenderBox { /* will be set when box is parented */ tree : tree::empty(), node : node, @@ -194,7 +262,7 @@ fn Box(id: int, node: Node, ctx: @FlowContext, kind: BoxData) -> Box { } } -impl @Box { +impl @RenderBox { pure fn is_replaced() -> bool { match self.kind { ImageBox(*) => true, // TODO: form elements, etc @@ -210,14 +278,12 @@ impl @Box { // FlowContext will combine the width of this element and // that of its children to arrive at the context width. GenericBox => au(0), + // TODO: consult CSS 'width', margin, border. // 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()) }) @@ -282,30 +348,99 @@ impl @Box { image } + + // TODO: to implement stacking contexts correctly, we need to + // create a set of display lists, one per each layer of a stacking + // context. (CSS 2.1, Section 9.9.1). Each box is passed the list + // set representing the box's stacking context. When asked to + // construct its constituent display items, each box puts its + // DisplayItems into the correct stack layer (according to CSS 2.1 + // Appendix E). and then builder flattens the list at the end. + + /* Methods for building a display list. This is a good candidate + for a function pointer as the number of boxes explodes. + + # Arguments + + * `builder` - the display list builder which manages the coordinate system and options. + * `dirty` - Dirty rectangle, in the coordinate system of the owning flow (self.ctx) + * `origin` - Total offset from display list root flow to this box's owning flow + * `list` - List to which items should be appended + */ + fn build_display_list(builder: &dl::DisplayListBuilder, dirty: &Rect, + offset: &Point2D, list: &dl::DisplayList) { + if !self.data.position.intersects(dirty) { + return; + } + + let bounds : Rect = Rect(self.data.position.origin.add(offset), + copy self.data.position.size); + + match self.kind { + TextBox(d) => { + let mut runs = d.runs; + // TODO: don't paint background for text boxes + 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 = make_glyph_run(&runs[i]); + list.push(~dl::Glyphs(bounds, glyph_run)); + bounds.origin.y += bounds.size.height; + } + return; + + pure fn make_glyph_run(text_run: &TextRun) -> dl::GlyphRun { + dl::GlyphRun { + glyphs: copy text_run.glyphs + } + } + }, + // TODO: items for background, border, outline + GenericBox(*) => { }, + ImageBox(*) => { + match self.get_image() { + Some(image) => list.push(~dl::Image(bounds, image)), + /* No image data at all? Okay, add some fallback content instead. */ + None => { + // TODO: shouldn't need to unbox CSSValue by now + let boxed_color = self.node.style().background_color; + let color = match boxed_color { + Specified(BgColor(c)) => c, + Specified(BgTransparent) | _ => util::color::rgba(0,0,0,0.0) + }; + list.push(~dl::SolidColor(bounds, color.red, color.green, color.blue)); + } + } + } + } + } } // FIXME: Why do these have to be redefined for each node type? -/* The tree holding boxes */ -enum BoxTree { BoxTree } +/* The tree holding render box relations. (This should only be used +for painting nested inlines, AFAIK-- everything else depends on Flow tree) */ +enum RenderBoxTree { RenderBoxTree } -impl BoxTree : tree::ReadMethods<@Box> { - fn each_child(node: @Box, f: fn(&&@Box) -> bool) { +impl RenderBoxTree : tree::ReadMethods<@RenderBox> { + fn each_child(node: @RenderBox, f: fn(&&@RenderBox) -> bool) { tree::each_child(self, node, f) } - fn with_tree_fields(&&b: @Box, f: fn(tree::Tree<@Box>) -> R) -> R { + fn with_tree_fields(&&b: @RenderBox, f: fn(tree::Tree<@RenderBox>) -> R) -> R { f(b.tree) } } -impl BoxTree : tree::WriteMethods<@Box> { - fn add_child(parent: @Box, child: @Box) { +impl RenderBoxTree : tree::WriteMethods<@RenderBox> { + fn add_child(parent: @RenderBox, child: @RenderBox) { 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 { + fn with_tree_fields(&&b: @RenderBox, f: fn(tree::Tree<@RenderBox>) -> R) -> R { f(b.tree) } } @@ -354,7 +489,7 @@ impl @FlowContext : DebugMethods { } } -impl @Box : DebugMethods { +impl @RenderBox : DebugMethods { fn dump() { self.dump_indent(0u); } @@ -369,7 +504,7 @@ impl @Box : DebugMethods { s += self.debug_str(); debug!("%s", s); - for BoxTree.each_child(self) |kid| { + for RenderBoxTree.each_child(self) |kid| { kid.dump_indent(indent + 1u) } } @@ -412,9 +547,9 @@ mod test { } */ - fn flat_bounds(root: @Box) -> ~[Rect] { + fn flat_bounds(root: @RenderBox) -> ~[Rect] { let mut r = ~[]; - for tree::each_child(BoxTree, root) |c| { + for tree::each_child(RenderBoxTree, root) |c| { push_all(r, flat_bounds(c)); } diff --git a/src/servo/layout/block.rs b/src/servo/layout/block.rs index ea566b2b780..d0bb7ed1547 100644 --- a/src/servo/layout/block.rs +++ b/src/servo/layout/block.rs @@ -1,14 +1,16 @@ use au = gfx::geometry; use css::values::*; +use dl = gfx::display_list; use geom::point::Point2D; +use geom::rect::Rect; use geom::size::Size2D; use gfx::geometry::au; -use layout::base::{Box, FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow}; +use layout::base::{RenderBox, FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow}; use layout::context::LayoutContext; use util::tree; struct BlockFlowData { - mut box: Option<@Box> + mut box: Option<@RenderBox> } fn BlockFlowData() -> BlockFlowData { @@ -20,11 +22,14 @@ fn BlockFlowData() -> BlockFlowData { trait BlockLayout { pure fn starts_block_flow() -> bool; pure fn access_block(fn(&&BlockFlowData) -> T) -> T; - pure fn with_block_box(fn(&&@Box) -> ()) -> (); + pure fn with_block_box(fn(&&@RenderBox) -> ()) -> (); fn bubble_widths_block(ctx: &LayoutContext); fn assign_widths_block(ctx: &LayoutContext); fn assign_height_block(ctx: &LayoutContext); + + fn build_display_list_block(a: &dl::DisplayListBuilder, b: &Rect, + c: &Point2D, d: &dl::DisplayList); } impl @FlowContext : BlockLayout { @@ -45,7 +50,7 @@ impl @FlowContext : BlockLayout { /* 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) -> ()) -> () { + pure fn with_block_box(cb:fn(&&@RenderBox) -> ()) -> () { match self.kind { BlockFlow(*) => { do self.access_block |d| { @@ -147,4 +152,22 @@ impl @FlowContext : BlockLayout { let (used_top, used_bot) = box.get_used_height(); } } + + fn build_display_list_block(builder: &dl::DisplayListBuilder, dirty: &Rect, + offset: &Point2D, list: &dl::DisplayList) { + + assert self.starts_block_flow(); + + // add box that starts block context + do self.with_block_box |box| { + box.build_display_list(builder, dirty, offset, list) + } + + // TODO: handle any out-of-flow elements + + // go deeper into the flow tree + for FlowTree.each_child(self) |child| { + self.build_display_list_for_child(builder, child, dirty, offset, list) + } + } } diff --git a/src/servo/layout/box_builder.rs b/src/servo/layout/box_builder.rs index 25431ef48f6..d0089d423db 100644 --- a/src/servo/layout/box_builder.rs +++ b/src/servo/layout/box_builder.rs @@ -5,7 +5,7 @@ use css::values::{CSSDisplay, DisplayBlock, DisplayInline, DisplayInlineBlock, D 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::{RenderBox, BoxData, GenericBox, ImageBox, TextBox, RenderBoxTree}; use layout::base::{FlowContext, FlowContextData, BlockFlow, InlineFlow, InlineBlockFlow, RootFlow, FlowTree}; use layout::block::BlockFlowData; use layout::context::LayoutContext; @@ -20,7 +20,7 @@ use servo_text::font_cache::FontCache; export LayoutTreeBuilder; struct LayoutTreeBuilder { - mut root_box: Option<@Box>, + mut root_box: Option<@RenderBox>, mut root_ctx: Option<@FlowContext>, mut next_bid: int, mut next_cid: int @@ -42,7 +42,7 @@ impl LayoutTreeBuilder { /** Creates necessary box(es) and flow context(s) for the current DOM node, and recurses on its children. */ - fn construct_recursively(layout_ctx: &LayoutContext, cur_node: Node, parent_ctx: @FlowContext, parent_box: @Box) { + fn construct_recursively(layout_ctx: &LayoutContext, cur_node: Node, parent_ctx: @FlowContext, parent_box: @RenderBox) { let style = cur_node.style(); // DEBUG @@ -109,7 +109,7 @@ impl LayoutTreeBuilder { // 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); + RenderBoxTree.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); @@ -146,7 +146,7 @@ impl LayoutTreeBuilder { /** entry point for box creation. Should only be called on root DOM element. */ - fn construct_trees(layout_ctx: &LayoutContext, root: Node) -> Result<@Box, ()> { + fn construct_trees(layout_ctx: &LayoutContext, root: Node) -> Result<@RenderBox, ()> { self.root_ctx = Some(self.make_ctx(RootFlow(RootFlowData()), tree::empty())); self.root_box = Some(self.make_box(root, self.root_ctx.get(), GenericBox)); @@ -160,8 +160,8 @@ impl LayoutTreeBuilder { ret } - fn make_box(node : Node, ctx: @FlowContext, data: BoxData) -> @Box { - let ret = @Box(self.next_box_id(), node, ctx, data); + fn make_box(node : Node, ctx: @FlowContext, data: BoxData) -> @RenderBox { + let ret = @RenderBox(self.next_box_id(), node, ctx, data); debug!("Created box: %s", ret.debug_str()); ret } diff --git a/src/servo/layout/display_list_builder.rs b/src/servo/layout/display_list_builder.rs index 6dbb98189cd..b62a5756d28 100644 --- a/src/servo/layout/display_list_builder.rs +++ b/src/servo/layout/display_list_builder.rs @@ -1,7 +1,7 @@ -export build_display_list; +export DisplayListBuilder; use au = gfx::geometry; -use base::Box; +use base::{RenderBox, RenderBoxTree}; use css::values::{BgColor, BgTransparent, Specified}; use dl = gfx::display_list; use dom::base::{Text, NodeScope}; @@ -13,118 +13,51 @@ use geom::rect::Rect; use geom::size::Size2D; use gfx::geometry::au; use layout::text::TextBoxData; -use layout::base::{TextBox, BoxTree}; +use layout::base::{LayoutContext, FlowContext, TextBox}; use servo_text::text_run::TextRun; use util::tree; use vec::push; -/** -Builds a display list for a box and all its children -*/ -fn build_display_list(box : @Box) -> dl::DisplayList { - let list = DVec(); - build_display_list_from_origin(list, box, Point2D(au(0), au(0))); - return list; +/** A builder object that manages display list builder should mainly + hold information about the initial request and desired result---for + example, whether the DisplayList to be used for painting or hit + testing. This can affect which boxes are created. + + Right now, the builder isn't used for much, but it establishes the + pattern we'll need once we support DL-based hit testing &c. */ +struct DisplayListBuilder { + ctx: &LayoutContext, } -/** -Builds a display list for a box and all its children. -# Arguments +trait FlowDisplayListBuilderMethods { + fn build_display_list(a: &DisplayListBuilder, b: &Rect, c: &dl::DisplayList); -* `box` - The box to build the display list for. -* `origin` - The coordinates of upper-left corner of the box containing the - passed-in box. + fn build_display_list_for_child(a: &DisplayListBuilder, b: @FlowContext, + c: &Rect, d: &Point2D, e: &dl::DisplayList); +} -*/ -fn build_display_list_from_origin(list: dl::DisplayList, box: @Box, origin: Point2D) { - let box_origin = Point2D( - 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); +impl @FlowContext: FlowDisplayListBuilderMethods { - box_to_display_items(list, box, box_origin); + fn build_display_list(builder: &DisplayListBuilder, dirty: &Rect, list: &dl::DisplayList) { + let zero = au::zero_point(); + self.build_display_list_recurse(builder, dirty, &zero, list); + } - for BoxTree.each_child(box) |c| { - debug!("Recursively building display list with origin %?", box_origin); - build_display_list_from_origin(list, c, box_origin); - } -} - -/** -Creates a display list item for a single block. - -# Arguments - -* `box` - The box to build the display list for -* `origin` - The coordinates of upper-left corner of the passed in box. - -*/ -#[allow(non_implicitly_copyable_typarams)] -fn box_to_display_items(list: dl::DisplayList, box: @Box, origin: Point2D) { - - // TODO: each box should know how to make its own display items. - // The display list builder should mainly hold information about - // the initial request and desired result---for example, is the - // DisplayList to be used for painting or hit testing. This can - // influence which boxes are created. - - // TODO: to implement stacking contexts correctly, we need to - // create a set of display lists, one per each layer of a stacking - // context. (CSS 2.1, Section 9.9.1). Each box is passed the list - // set representing the box's stacking context. When asked to - // construct its constituent display items, each box puts its - // DisplayItems into the correct stack layer (according to CSS 2.1 - // Appendix E). and then builder flattens the list at the end. - - debug!("request to display a box from origin %?", origin); - - let bounds : Rect = Rect(origin, copy box.data.position.size); - - match box.kind { - 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(& 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: &TextRun) -> - dl::GlyphRun { - dl::GlyphRun { - glyphs: copy text_run.glyphs - } - } - } - _ => { - // Fall through - } - }; - - // Check if there is a background image, if not set the background color. - 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.style().background_color; - let color = match boxed_color { - Specified(BgColor(c)) => c, - Specified(BgTransparent) | _ => util::color::rgba(0,0,0,0.0) - }; - debug!("Assigning color %? to box with bounds %?", color, bounds); - list.push(~dl::SolidColor(bounds, color.red, color.green, color.blue)); + fn build_display_list_for_child(builder: &DisplayListBuilder, child: @FlowContext, + dirty: &Rect, offset: &Point2D, list: &dl::DisplayList) { + + // adjust the dirty rect to child flow context coordinates + let adj_dirty = dirty.translate(&child.data.position.origin); + let adj_offset = offset.add(&child.data.position.origin); + + if (adj_dirty.intersects(&child.data.position)) { + child.build_display_list_recurse(builder, &adj_dirty, &adj_offset, list); + } } } +/* TODO: redo unit tests, if possible?gn fn should_convert_text_boxes_to_solid_color_background_items() { #[test]; @@ -212,3 +145,4 @@ fn should_calculate_the_bounds_of_the_text_items() { do list.borrow |l| { assert l[1].bounds == expected; } } +*/ \ No newline at end of file diff --git a/src/servo/layout/inline.rs b/src/servo/layout/inline.rs index 29907d11236..f052dcf2ba9 100644 --- a/src/servo/layout/inline.rs +++ b/src/servo/layout/inline.rs @@ -1,18 +1,44 @@ use au = gfx::geometry; -use base::Box; +use base::RenderBox; use core::dvec::DVec; use css::values::{BoxAuto, BoxLength, Px}; +use dl = gfx::display_list; use dom::rcu; use geom::point::Point2D; +use geom::rect::Rect; use geom::size::Size2D; use gfx::geometry::au; use layout::context::LayoutContext; -use layout::base::{FlowContext, InlineFlow, BoxTree, ImageBox, TextBox, GenericBox}; +use layout::base::{FlowContext, InlineFlow, RenderBoxTree, ImageBox, TextBox, GenericBox}; use num::Num; use util::tree; +/* +Tentative design: (may not line up with reality) + +Lineboxes are represented as offsets into the child list, rather than +as an object that "owns" boxes. Choosing a different set of line +breaks requires a new list of offsets, and possibly some splitting and +merging of TextBoxes. + +A similar list will keep track of the mapping between CSS boxes and +the corresponding render boxes in the inline flow. + +After line breaks are determined, lender boxes in the inline flow may +overlap visually. For example, in the case of nested inline CSS boxes, +outer inlines must be at least as large as the inner inlines, for +purposes of drawing noninherited things like backgrounds, borders, +outlines. + +N.B. roc has an alternative design where the list instead consists of +things like "start outer box, text, start inner box, text, end inner +box, text, end outer box, text". This seems a little complicated to +serve as the starting point, but the current design doesn't make it +hard to try out that alternative. +*/ + struct InlineFlowData { - boxes: ~DVec<@Box> + boxes: ~DVec<@RenderBox> } fn InlineFlowData() -> InlineFlowData { @@ -28,6 +54,7 @@ trait InlineLayout { fn bubble_widths_inline(ctx: &LayoutContext); fn assign_widths_inline(ctx: &LayoutContext); fn assign_height_inline(ctx: &LayoutContext); + fn build_display_list_inline(a: &dl::DisplayListBuilder, b: &Rect, c: &Point2D, d: &dl::DisplayList); } impl @FlowContext : InlineLayout { @@ -116,4 +143,27 @@ impl @FlowContext : InlineLayout { // during inline flowing. } + fn build_display_list_inline(builder: &dl::DisplayListBuilder, dirty: &Rect, + offset: &Point2D, list: &dl::DisplayList) { + + assert self.starts_inline_flow(); + + // TODO: if the CSS box introducing this inline context is *not* anonymous, + // we need to draw it too, in a way similar to BlowFlowContext + + // TODO: once we form line boxes and have their cached bounds, we can be + // smarter and not recurse on a line if nothing in it can intersect dirty + do self.access_inline |d| { + for d.boxes.each |box| { + box.build_display_list(builder, dirty, offset, list) + } + } + + // TODO: should inline-block elements have flows as children + // of the inline flow, or should the flow be nested inside the + // box somehow? Maybe it's best to unify flows and boxes into + // the same enum, so inline-block flows are normal + // (indivisible) children in the inline flow child list. + } + } // @FlowContext : InlineLayout diff --git a/src/servo/layout/layout_task.rs b/src/servo/layout/layout_task.rs index d71a4050a68..9aa88977e6c 100644 --- a/src/servo/layout/layout_task.rs +++ b/src/servo/layout/layout_task.rs @@ -6,16 +6,17 @@ use au = gfx::geometry; use au::au; use content::content_task; +use core::dvec::DVec; use css::resolve::apply::apply_style; use css::values::Stylesheet; -use display_list_builder::build_display_list; +use dl = gfx::display_list; use dom::base::Node; use dom::event::{Event, ReflowEvent}; use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; use gfx::render_task; -use layout::base::Box; +use layout::base::RenderBox; use layout::box_builder::LayoutTreeBuilder; use layout::context::LayoutContext; use render_task::RenderTask; @@ -67,14 +68,15 @@ fn LayoutTask(render_task: RenderTask, image_cache_task: ImageCacheTask) -> Layo layout_data_refs += node.initialize_style_for_subtree(); node.recompute_style_for_subtree(&layout_ctx, styles); - let root_box: @Box; + // TODO: this should care about root flow, not root box. + let root_box: @RenderBox; let builder = LayoutTreeBuilder(); match builder.construct_trees(&layout_ctx, node) { Ok(root) => root_box = root, Err(*) => fail ~"Root node should always exist" } - debug!("layout: constructed Box tree"); + debug!("layout: constructed RenderBox tree"); root_box.dump(); debug!("layout: constructed Flow tree"); @@ -90,7 +92,13 @@ fn LayoutTask(render_task: RenderTask, image_cache_task: ImageCacheTask) -> Layo do root_flow.traverse_preorder |f| { f.assign_widths(&layout_ctx) } do root_flow.traverse_postorder |f| { f.assign_height(&layout_ctx) } - let dlist = build_display_list(root_box); + let dlist = DVec(); + let builder = dl::DisplayListBuilder { + ctx: &layout_ctx, + }; + // TODO: set options on the builder before building + // TODO: be smarter about what needs painting + root_flow.build_display_list(&builder, © root_flow.data.position, &dlist); render_task.send(render_task::RenderMsg(dlist)); } } diff --git a/src/servo/layout/root.rs b/src/servo/layout/root.rs index 96070d94c9f..e3a0ca35cfc 100644 --- a/src/servo/layout/root.rs +++ b/src/servo/layout/root.rs @@ -1,12 +1,15 @@ use au = gfx::geometry; use css::values::*; +use dl = gfx::display_list; +use geom::point::Point2D; +use geom::rect::Rect; use gfx::geometry::au; -use layout::base::{Box, FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow}; +use layout::base::{RenderBox, FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow}; use layout::context::LayoutContext; use util::tree; struct RootFlowData { - mut box: Option<@Box> + mut box: Option<@RenderBox> } fn RootFlowData() -> RootFlowData { @@ -22,6 +25,8 @@ trait RootLayout { fn bubble_widths_root(ctx: &LayoutContext); fn assign_widths_root(ctx: &LayoutContext); fn assign_height_root(ctx: &LayoutContext); + + fn build_display_list_root(a: &dl::DisplayListBuilder, b: &Rect, c: &Point2D, d: &dl::DisplayList); } impl @FlowContext : RootLayout { @@ -58,4 +63,11 @@ impl @FlowContext : RootLayout { self.assign_height_block(ctx); } + + fn build_display_list_root(builder: &dl::DisplayListBuilder, dirty: &Rect, + offset: &Point2D, list: &dl::DisplayList) { + assert self.starts_root_flow(); + + self.build_display_list_block(builder, dirty, offset, list); + } } diff --git a/src/servo/layout/text.rs b/src/servo/layout/text.rs index b2335f870b6..ca39b48836c 100644 --- a/src/servo/layout/text.rs +++ b/src/servo/layout/text.rs @@ -5,7 +5,7 @@ use geom::size::Size2D; use gfx::geometry::au; use servo_text::text_run::TextRun; use servo_text::font_cache::FontCache; -use layout::base::{TextBox, Box}; +use layout::base::{TextBox, RenderBox}; use layout::context::LayoutContext; struct TextBoxData { @@ -25,7 +25,7 @@ trait TextLayout { } #[doc="The main reflow routine for text layout."] -impl @Box : TextLayout { +impl @RenderBox : TextLayout { fn reflow_text(ctx: &LayoutContext) { let d = match self.kind { TextBox(d) => { d } @@ -78,6 +78,7 @@ impl @Box : TextLayout { } } +/* TODO: new unit tests for TextBox splitting, etc fn should_calculate_the_size_of_the_text_box() { #[test]; #[ignore(cfg(target_os = "macos"))]; @@ -97,3 +98,4 @@ fn should_calculate_the_size_of_the_text_box() { let expected = Size2D(au::from_px(84), au::from_px(20)); assert b.data.position.size == expected; } +*/ \ No newline at end of file diff --git a/src/servo/layout/traverse.rs b/src/servo/layout/traverse.rs index ad2c5fd22e4..a92564c4944 100644 --- a/src/servo/layout/traverse.rs +++ b/src/servo/layout/traverse.rs @@ -1,16 +1,17 @@ /** Interface for running tree-based traversals over layout boxes and contextsg */ -use layout::base::{Box, BoxTree}; +use layout::base::{RenderBox, RenderBoxTree}; use layout::base::{FlowContext, FlowTree}; -trait BoxTraversals { - fn traverse_preorder(preorder_cb: &fn(@Box)); +/* TODO: we shouldn't need render box traversals */ +trait RenderBoxTraversals { + fn traverse_preorder(preorder_cb: &fn(@RenderBox)); } -impl @Box : BoxTraversals { - fn traverse_preorder(preorder_cb: &fn(@Box)) { +impl @RenderBox : RenderBoxTraversals { + fn traverse_preorder(preorder_cb: &fn(@RenderBox)) { preorder_cb(self); - do BoxTree.each_child(self) |child| { child.traverse_preorder(preorder_cb); true } + do RenderBoxTree.each_child(self) |child| { child.traverse_preorder(preorder_cb); true } } } diff --git a/src/servo/layout/traverse_parallel.rs b/src/servo/layout/traverse_parallel.rs index c7c7271cc78..802604242f7 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, BoxTree}; +use base::{RenderBox, RenderBoxTree}; use intrinsic::TyDesc; export full_traversal; @@ -22,14 +22,14 @@ type shared_box = { }; #[doc="Transform and @ into its underlying representation. The reference count stays constant."] -fn unwrap_box(-b : @Box) -> *shared_box unsafe { - let new_box : *shared_box = unsafe::transmute(b); +fn unwrap_box(-b : @RenderBox) -> *shared_box unsafe { + let new_box : *shared_box = unsafe::transmute(b); return new_box; } #[doc="Transform an underlying representation back to an @. The reference count stays constant."] -fn rewrap_box(-b : *shared_box) -> @Box unsafe { - let new_box : @Box = unsafe::transmute(b); +fn rewrap_box(-b : *shared_box) -> @RenderBox unsafe { + let new_box : @RenderBox = unsafe::transmute(b); return new_box; } @@ -53,8 +53,8 @@ finish, and then applies the second function to the current box. applied to that node's children "] -fn traverse_helper(-root : @Box, returned : T, -top_down : fn~(+T, @Box) -> T, - -bottom_up : fn~(@Box)) { +fn traverse_helper(-root : @RenderBox, returned : T, -top_down : fn~(+T, @RenderBox) -> T, + -bottom_up : fn~(@RenderBox)) { let returned = top_down(returned, root); do listen |ack_chan| { @@ -67,21 +67,21 @@ 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 BoxTree.each_child(root) |kid| { + for RenderBoxTree.each_child(root) |kid| { count += 1; // Unwrap the box so we can send it out of this task let unwrapped = unwrap_box(copy kid); // Hide the box in an option so we can get it across the // task boundary without copying it - let swappable : ~mut Option<*shared_box> = ~mut Some(unwrapped); + let swappable : ~mut Option<*shared_box> = ~mut Some(unwrapped); do task::spawn |copy top_down, copy bottom_up| { // Get the box out of the option and into the new task let mut swapped_in = None; swapped_in <-> *swappable; - // Retrieve the original @Box and recurse + // Retrieve the original @RenderBox and recurse let new_kid = rewrap_box(option::unwrap(swapped_in)); traverse_helper(new_kid, copy returned, copy top_down, copy bottom_up); @@ -97,7 +97,7 @@ fn traverse_helper(-root : @Box, returned : T, -top_down : fn~(+T } #[doc="A noneffectful function to be used if only one pass is required."] -fn nop(_box : @Box) { +fn nop(_box : @RenderBox) { return; } @@ -105,15 +105,15 @@ fn nop(_box : @Box) { A wrapper to change a function that only acts on a box to one that threasds a unit through to match travserse_helper "] -fn unit_wrapper(-fun : fn~(@Box)) -> fn~(+(), @Box) { - fn~(+_u : (), box : @Box) { fun(box); } +fn unit_wrapper(-fun : fn~(@RenderBox)) -> fn~(+(), @RenderBox) { + fn~(+_u : (), box : @RenderBox) { fun(box); } } #[doc=" Iterate in parallel over the boxes in a tree, applying one function to a parent before recursing on its children and one after. "] -fn full_traversal(+root : @Box, -top_down : fn~(@Box), -bottom_up : fn~(@Box)) { +fn full_traversal(+root : @RenderBox, -top_down : fn~(@RenderBox), -bottom_up : fn~(@RenderBox)) { traverse_helper(root, (), unit_wrapper(top_down), bottom_up); } @@ -121,7 +121,7 @@ fn full_traversal(+root : @Box, -top_down : fn~(@Box), -bottom_up : fn~(@Box)) { Iterate in parallel over the boxes in a tree, applying the given function to a parent before its children. "] -fn top_down_traversal(+root : @Box, -top_down : fn~(@Box)) { +fn top_down_traversal(+root : @RenderBox, -top_down : fn~(@RenderBox)) { traverse_helper(root, (), unit_wrapper(top_down), nop); } @@ -129,7 +129,7 @@ fn top_down_traversal(+root : @Box, -top_down : fn~(@Box)) { Iterate in parallel over the boxes in a tree, applying the given function to a parent after its children. "] -fn bottom_up_traversal(+root : @Box, -bottom_up : fn~(@Box)) { +fn bottom_up_traversal(+root : @RenderBox, -bottom_up : fn~(@RenderBox)) { traverse_helper(root, (), unit_wrapper(nop), bottom_up); } @@ -140,9 +140,9 @@ fn bottom_up_traversal(+root : @Box, -bottom_up : fn~(@Box)) { the recursion unwinds, the second function is applied to first the children in parallel, and then the parent. "] -fn extended_full_traversal(+root : @Box, first_val : T, - -top_down : fn~(+T, @Box) -> T, - -bottom_up : fn~(@Box)) { +fn extended_full_traversal(+root : @RenderBox, first_val : T, + -top_down : fn~(+T, @RenderBox) -> T, + -bottom_up : fn~(@RenderBox)) { traverse_helper(root, first_val, top_down, bottom_up); } @@ -151,7 +151,7 @@ fn extended_full_traversal(+root : @Box, first_val : T, function to a parent before its children, the value returned by the function is passed to each child when they are recursed upon. "] -fn extended_top_down_traversal(+root : @Box, first_val : T, - -top_down : fn~(+T, @Box) -> T) { +fn extended_top_down_traversal(+root : @RenderBox, first_val : T, + -top_down : fn~(+T, @RenderBox) -> T) { traverse_helper(root, first_val, top_down, nop); }