From e7dd2610c934f7d23f35be41faeba9920201296c Mon Sep 17 00:00:00 2001 From: "Brian J. Burg" Date: Wed, 10 Oct 2012 15:53:45 -0700 Subject: [PATCH] Build inline flow box list using BoxConsumer. Fix failure cases in FlowContext variant helper methods to only print flow/box id, so they don't run out of stack when reflectively printing cycles. --- src/servo/layout/block.rs | 6 +-- src/servo/layout/box.rs | 18 +++++++ src/servo/layout/box_builder.rs | 11 ++-- src/servo/layout/flow.rs | 91 ++++++++++++++++++++++++++++----- src/servo/layout/inline.rs | 4 +- 5 files changed, 109 insertions(+), 21 deletions(-) diff --git a/src/servo/layout/block.rs b/src/servo/layout/block.rs index 9531dd07ced..391535a171b 100644 --- a/src/servo/layout/block.rs +++ b/src/servo/layout/block.rs @@ -22,7 +22,7 @@ fn BlockFlowData() -> BlockFlowData { trait BlockLayout { pure fn starts_block_flow() -> bool; - pure fn with_block_box(fn(box: &@RenderBox) -> ()) -> (); + pure fn with_block_box(@self, fn(box: &@RenderBox) -> ()) -> (); fn bubble_widths_block(@self, ctx: &LayoutContext); fn assign_widths_block(@self, ctx: &LayoutContext); @@ -42,8 +42,8 @@ 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: &@RenderBox) -> ()) -> () { - match self { + pure fn with_block_box(@self, cb: fn(box: &@RenderBox) -> ()) -> () { + match *self { BlockFlow(*) => { let mut box = self.block().box; box.iter(cb); diff --git a/src/servo/layout/box.rs b/src/servo/layout/box.rs index 90a19bb8c9c..e92b6813134 100644 --- a/src/servo/layout/box.rs +++ b/src/servo/layout/box.rs @@ -91,12 +91,18 @@ pub enum RenderBox { UnscannedTextBox(RenderBoxData, ~str) } +enum InlineSpacerSide { + LogicalBefore, + LogicalAfter, +} + trait RenderBoxMethods { pure fn d(&self) -> &self/RenderBoxData; pure fn is_replaced() -> bool; pure fn can_split() -> bool; pure fn can_merge_with_box(@self, other: @RenderBox) -> bool; + pure fn requires_inline_spacers() -> bool; pure fn content_box() -> Rect; pure fn border_box() -> Rect; @@ -104,6 +110,7 @@ trait RenderBoxMethods { fn get_pref_width(&LayoutContext) -> au; fn get_used_width() -> (au, au); fn get_used_height() -> (au, au); + fn create_inline_spacer_for_side(&LayoutContext, InlineSpacerSide) -> Option<@RenderBox>; fn build_display_list(&dl::DisplayListBuilder, dirty: &Rect, offset: &Point2D, &dl::DisplayList); } @@ -226,6 +233,11 @@ impl RenderBox : RenderBoxMethods { (au(0), au(0)) } + /* Whether "spacer" boxes are needed to stand in for this DOM node */ + pure fn requires_inline_spacers() -> bool { + return false; + } + /* The box formed by the content edge, as defined in CSS 2.1 Section 8.1. Coordinates are relative to the owning flow. */ pure fn content_box() -> Rect { @@ -267,6 +279,12 @@ impl RenderBox : RenderBoxMethods { self.content_box() } + + // TODO: implement this, generating spacer + fn create_inline_spacer_for_side(_ctx: &LayoutContext, _side: InlineSpacerSide) -> Option<@RenderBox> { + None + } + // 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 diff --git a/src/servo/layout/box_builder.rs b/src/servo/layout/box_builder.rs index 2ba6629a08e..949ae903236 100644 --- a/src/servo/layout/box_builder.rs +++ b/src/servo/layout/box_builder.rs @@ -88,15 +88,19 @@ impl LayoutTreeBuilder { } }; - let mut builder_ctx : BuilderContext = copy *parent_ctx; + let builder_ctx : BuilderContext; // if this is a new flow, attach to parent flow and make a new BuilderContext. if !core::box::ptr_eq(next_flow, parent_ctx.flow) { + debug!("using parent builder context"); debug!("Adding child flow f%? of f%?", parent_ctx.flow.d().id, next_flow.d().id); FlowTree.add_child(parent_ctx.flow, next_flow); builder_ctx = { flow: next_flow, consumer: BoxConsumer(next_flow) }; + } else { + debug!("creating fresh builder context"); + builder_ctx = copy *parent_ctx; } // store reference to the flow context which contains any @@ -106,16 +110,17 @@ impl LayoutTreeBuilder { assert cur_node.has_aux(); do cur_node.aux |data| { data.flow = Some(builder_ctx.flow) } - let new_box = self.make_box(layout_ctx, box_type, cur_node, builder_ctx.flow); debug!("Assign ^box to flow: %?", builder_ctx.flow.debug_str()); - builder_ctx.consumer.accept_box(layout_ctx, new_box); + builder_ctx.consumer.push_box(layout_ctx, new_box); // recurse on child nodes. do NodeTree.each_child(&cur_node) |child_node| { self.construct_recursively(layout_ctx, *child_node, &builder_ctx); true } + builder_ctx.consumer.pop_box(layout_ctx, new_box); + // Fixup any irregularities, such as split inlines (CSS 2.1 Section 9.2.1.1) if (builder_ctx.flow.starts_inline_flow()) { let mut found_child_inline = false; diff --git a/src/servo/layout/flow.rs b/src/servo/layout/flow.rs index 0fda10db7c9..e722c0cb3bc 100644 --- a/src/servo/layout/flow.rs +++ b/src/servo/layout/flow.rs @@ -1,12 +1,13 @@ use au = gfx::geometry; use au::au; +use core::dvec::DVec; use dl = gfx::display_list; use dom::node::Node; use geom::rect::Rect; use geom::point::Point2D; // TODO: pub-use these use layout::block::BlockFlowData; -use layout::box::RenderBox; +use layout::box::{LogicalBefore, LogicalAfter, RenderBox}; use layout::context::LayoutContext; use layout::debug::BoxedDebugMethods; use layout::inline::InlineFlowData; @@ -105,31 +106,94 @@ fn FlowData(id: int) -> FlowData { } } +struct PendingEntry { + start_box: @RenderBox, + start_idx: uint +} + // helper object for building the initial box list and making the // mapping between DOM nodes and boxes. struct BoxConsumer { flow: @FlowContext, + stack: DVec, } fn BoxConsumer(flow: @FlowContext) -> BoxConsumer { + debug!("Creating box consumer for flow: f%s", flow.debug_str()); BoxConsumer { flow: flow, + stack: DVec() } } impl BoxConsumer { - pub fn accept_box(_ctx: &LayoutContext, box: @RenderBox) { + pub fn push_box(ctx: &LayoutContext, box: @RenderBox) { + debug!("BoxConsumer: pushing box b%d to flow f%d", box.d().id, self.flow.d().id); + let length = match self.flow { + @InlineFlow(*) => self.flow.inline().boxes.len(), + _ => 0 + }; + let entry = PendingEntry { start_box: box, start_idx: length }; + self.stack.push(entry); + match self.flow { - @InlineFlow(*) => self.flow.inline().boxes.push(box), + @InlineFlow(*) => { + if box.requires_inline_spacers() { + do box.create_inline_spacer_for_side(ctx, LogicalBefore).iter |b: &@RenderBox| { + self.flow.inline().boxes.push(*b); + } + } + }, + @BlockFlow(*) | @RootFlow(*) => { + assert self.stack.len() == 1; + }, + _ => { warn!("push_box() not implemented for flow f%d", self.flow.d().id) } + } + } + + pub fn pop_box(ctx: &LayoutContext, box: @RenderBox) { + assert self.stack.len() > 0; + let entry = self.stack.pop(); + assert core::box::ptr_eq(box, entry.start_box); + + debug!("BoxConsumer: popping box b%d to flow f%d", box.d().id, self.flow.d().id); + + match self.flow { + @InlineFlow(*) => { + let pre_length = self.flow.inline().boxes.len() - entry.start_idx; + match (pre_length, box.requires_inline_spacers()) { + // leaf box + (0, _) => { self.flow.inline().boxes.push(box); }, + // if this non-leaf box generates extra horizontal + // spacing, add a SpacerBox for it. + (_, true) => { + do box.create_inline_spacer_for_side(ctx, LogicalAfter).iter |b: &@RenderBox| { + self.flow.inline().boxes.push(*b); + } + }, + // non-leaf with no spacer; do nothing + (_, false) => { } + } + + let post_length = self.flow.inline().boxes.len() - entry.start_idx; + let mapping = { node: copy box.d().node, + span: { + start: entry.start_idx as u8, + len: post_length as u8 + } + }; + debug!("BoxConsumer: adding element range %?", mapping.span); + self.flow.inline().elems.push(mapping); + }, @BlockFlow(*) => { assert self.flow.block().box.is_none(); - self.flow.block().box = Some(box); + self.flow.block().box = Some(entry.start_box); }, @RootFlow(*) => { - assert self.flow.block().box.is_none(); - self.flow.block().box = Some(box); + assert self.flow.root().box.is_none(); + self.flow.root().box = Some(entry.start_box); }, - _ => { warn!("accept_box not implemented for flow %?", self.flow.d().id) } + _ => { warn!("pop_box not implemented for flow %?", self.flow.d().id) } } } } @@ -150,21 +214,21 @@ impl FlowContext : FlowContextMethods { pure fn inline(&self) -> &self/InlineFlowData { match *self { InlineFlow(_, ref i) => i, - _ => fail fmt!("Tried to access inline data of non-inline: %?", self) + _ => fail fmt!("Tried to access inline data of non-inline: f%d", self.d().id) } } pure fn block(&self) -> &self/BlockFlowData { match *self { BlockFlow(_, ref b) => b, - _ => fail fmt!("Tried to access block data of non-block: %?", self) + _ => fail fmt!("Tried to access block data of non-block: f%d", self.d().id) } } pure fn root(&self) -> &self/RootFlowData { match *self { RootFlow(_, ref r) => r, - _ => fail fmt!("Tried to access root data of non-root: %?", self) + _ => fail fmt!("Tried to access root data of non-root: f%d", self.d().id) } } @@ -173,7 +237,7 @@ impl FlowContext : FlowContextMethods { @BlockFlow(*) => self.bubble_widths_block(ctx), @InlineFlow(*) => self.bubble_widths_inline(ctx), @RootFlow(*) => self.bubble_widths_root(ctx), - _ => fail fmt!("Tried to bubble_widths of flow: %?", self) + _ => fail fmt!("Tried to bubble_widths of flow: f%d", self.d().id) } } @@ -182,7 +246,7 @@ impl FlowContext : FlowContextMethods { @BlockFlow(*) => self.assign_widths_block(ctx), @InlineFlow(*) => self.assign_widths_inline(ctx), @RootFlow(*) => self.assign_widths_root(ctx), - _ => fail fmt!("Tried to assign_widths of flow: %?", self) + _ => fail fmt!("Tried to assign_widths of flow: f%d", self.d().id) } } @@ -191,7 +255,7 @@ impl FlowContext : FlowContextMethods { @BlockFlow(*) => self.assign_height_block(ctx), @InlineFlow(*) => self.assign_height_inline(ctx), @RootFlow(*) => self.assign_height_root(ctx), - _ => fail fmt!("Tried to assign_height of flow: %?", self) + _ => fail fmt!("Tried to assign_height of flow: f%d", self.d().id) } } @@ -290,7 +354,6 @@ impl FlowContext : BoxedDebugMethods { } } - /* TODO: we need a string builder. This is horribly inefficient */ fn debug_str(@self) -> ~str { let repr = match *self { InlineFlow(*) => { diff --git a/src/servo/layout/inline.rs b/src/servo/layout/inline.rs index 1f6f2de8157..a913f92184a 100644 --- a/src/servo/layout/inline.rs +++ b/src/servo/layout/inline.rs @@ -3,6 +3,7 @@ use core::dlist::DList; use core::dvec::DVec; use css::values::{BoxAuto, BoxLength, Px}; use dl = gfx::display_list; +use dom::node::Node; use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; @@ -39,6 +40,7 @@ hard to try out that alternative. */ type BoxRange = {start: u8, len: u8}; +type NodeRange = {node: Node, span: BoxRange}; // stack-allocated object for scanning an inline flow into // TextRun-containing TextBoxes. @@ -173,7 +175,7 @@ struct InlineFlowData { // vec of ranges into boxes that represent elements. These // ranges must be disjoint or well-nested, and are only related to // the content of boxes (not lines) - elems: DVec + elems: DVec } fn InlineFlowData() -> InlineFlowData {