From 26d5cfb5098dc62117f8ff746ef868411fdb5bea Mon Sep 17 00:00:00 2001 From: "Brian J. Burg" Date: Tue, 9 Oct 2012 17:39:18 -0700 Subject: [PATCH] Implement TextRunScanner; add explicit @self to all flow-related functions. --- src/servo/layout/block.rs | 24 ++-- src/servo/layout/box.rs | 21 +++- src/servo/layout/display_list_builder.rs | 8 +- src/servo/layout/flow.rs | 32 ++--- src/servo/layout/inline.rs | 153 ++++++++++++++++++++--- src/servo/layout/root.rs | 17 +-- src/servo/layout/text.rs | 15 ++- src/servo/text/glyph.rs | 6 +- src/servo/text/text_run.rs | 12 +- 9 files changed, 221 insertions(+), 67 deletions(-) diff --git a/src/servo/layout/block.rs b/src/servo/layout/block.rs index c5a77be9a2c..00678809300 100644 --- a/src/servo/layout/block.rs +++ b/src/servo/layout/block.rs @@ -24,11 +24,11 @@ trait BlockLayout { pure fn starts_block_flow() -> bool; pure fn with_block_box(fn(box: &@RenderBox) -> ()) -> (); - fn bubble_widths_block(ctx: &LayoutContext); - fn assign_widths_block(ctx: &LayoutContext); - fn assign_height_block(ctx: &LayoutContext); + fn bubble_widths_block(@self, ctx: &LayoutContext); + fn assign_widths_block(@self, ctx: &LayoutContext); + fn assign_height_block(@self, ctx: &LayoutContext); - fn build_display_list_block(a: &dl::DisplayListBuilder, b: &Rect, + fn build_display_list_block(@self, a: &dl::DisplayListBuilder, b: &Rect, c: &Point2D, d: &dl::DisplayList); } @@ -66,14 +66,14 @@ impl FlowContext : BlockLayout { /* TODO: floats */ /* TODO: absolute contexts */ /* TODO: inline-blocks */ - fn bubble_widths_block(ctx: &LayoutContext) { + fn bubble_widths_block(@self, ctx: &LayoutContext) { 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| { + 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.d().min_width); @@ -98,7 +98,7 @@ impl FlowContext : BlockLayout { Dual boxes consume some width first, and the remainder is assigned to all child (block) contexts. */ - fn assign_widths_block(_ctx: &LayoutContext) { + fn assign_widths_block(@self, _ctx: &LayoutContext) { assert self.starts_block_flow(); let mut remaining_width = self.d().position.size.width; @@ -113,19 +113,19 @@ impl FlowContext : BlockLayout { remaining_width = remaining_width.sub(&left_used.add(&right_used)); } - for FlowTree.each_child(@self) |child_ctx| { + for FlowTree.each_child(self) |child_ctx| { assert child_ctx.starts_block_flow() || child_ctx.starts_inline_flow(); child_ctx.d().position.origin.x = left_used; child_ctx.d().position.size.width = remaining_width; } } - fn assign_height_block(_ctx: &LayoutContext) { + fn assign_height_block(@self, _ctx: &LayoutContext) { assert self.starts_block_flow(); let mut cur_y = au(0); - for FlowTree.each_child(@self) |child_ctx| { + for FlowTree.each_child(self) |child_ctx| { child_ctx.d().position.origin.y = cur_y; cur_y = cur_y.add(&child_ctx.d().position.size.height); } @@ -142,7 +142,7 @@ impl FlowContext : BlockLayout { } } - fn build_display_list_block(builder: &dl::DisplayListBuilder, dirty: &Rect, + fn build_display_list_block(@self, builder: &dl::DisplayListBuilder, dirty: &Rect, offset: &Point2D, list: &dl::DisplayList) { assert self.starts_block_flow(); @@ -155,7 +155,7 @@ impl FlowContext : BlockLayout { // TODO: handle any out-of-flow elements // go deeper into the flow tree - for FlowTree.each_child(@self) |child| { + for FlowTree.each_child(self) |child| { self.build_display_list_for_child(builder, child, dirty, offset, list) } } diff --git a/src/servo/layout/box.rs b/src/servo/layout/box.rs index e898c55a24e..dd0d9a0f700 100644 --- a/src/servo/layout/box.rs +++ b/src/servo/layout/box.rs @@ -87,7 +87,7 @@ struct RenderBoxData { enum RenderBoxType { RenderBox_Generic, RenderBox_Image, - RenderBox_Text + RenderBox_Text, } pub enum RenderBox { @@ -101,6 +101,8 @@ 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 content_box() -> Rect; pure fn border_box() -> Rect; @@ -141,6 +143,23 @@ impl RenderBox : RenderBoxMethods { } } + pure fn can_split() -> bool { + match self { + TextBox(*) => true, + _ => false + } + } + + pure fn can_merge_with_box(@self, other: @RenderBox) -> bool { + assert !core::box::ptr_eq(self, other); + + match (self, other) { + (@UnscannedTextBox(*), @UnscannedTextBox(*)) => true, + (@TextBox(_,d1), @TextBox(_,d2)) => { core::box::ptr_eq(d1.run, d2.run) } + (_, _) => false + } + } + /** In general, these functions are transitively impure because they * may cause glyphs to be allocated. For now, it's impure because of * holder.get_image() diff --git a/src/servo/layout/display_list_builder.rs b/src/servo/layout/display_list_builder.rs index 8d7943b4759..921d5b3dd89 100644 --- a/src/servo/layout/display_list_builder.rs +++ b/src/servo/layout/display_list_builder.rs @@ -32,19 +32,19 @@ pub struct DisplayListBuilder { trait FlowDisplayListBuilderMethods { - fn build_display_list(a: &DisplayListBuilder, b: &Rect, c: &dl::DisplayList); + fn build_display_list(@self, a: &DisplayListBuilder, b: &Rect, c: &dl::DisplayList); - fn build_display_list_for_child(a: &DisplayListBuilder, b: @FlowContext, + fn build_display_list_for_child(@self, a: &DisplayListBuilder, b: @FlowContext, c: &Rect, d: &Point2D, e: &dl::DisplayList); } impl FlowContext: FlowDisplayListBuilderMethods { - fn build_display_list(builder: &DisplayListBuilder, dirty: &Rect, list: &dl::DisplayList) { + fn build_display_list(@self, builder: &DisplayListBuilder, dirty: &Rect, list: &dl::DisplayList) { let zero = au::zero_point(); self.build_display_list_recurse(builder, dirty, &zero, list); } - fn build_display_list_for_child(builder: &DisplayListBuilder, child: @FlowContext, + fn build_display_list_for_child(@self, builder: &DisplayListBuilder, child: @FlowContext, dirty: &Rect, offset: &Point2D, list: &dl::DisplayList) { diff --git a/src/servo/layout/flow.rs b/src/servo/layout/flow.rs index 75efaf2ea98..a269bf85e93 100644 --- a/src/servo/layout/flow.rs +++ b/src/servo/layout/flow.rs @@ -130,39 +130,39 @@ fn FlowData(id: int) -> FlowData { /* Flow context disambiguation methods: the verbose alternative to virtual methods */ impl FlowContext { - fn bubble_widths(ctx: &LayoutContext) { + fn bubble_widths(@self, ctx: &LayoutContext) { match self { - BlockFlow(*) => self.bubble_widths_block(ctx), - InlineFlow(*) => self.bubble_widths_inline(ctx), - RootFlow(*) => self.bubble_widths_root(ctx), + @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) } } - fn assign_widths(ctx: &LayoutContext) { + fn assign_widths(@self, ctx: &LayoutContext) { match self { - BlockFlow(*) => self.assign_widths_block(ctx), - InlineFlow(*) => self.assign_widths_inline(ctx), - RootFlow(*) => self.assign_widths_root(ctx), + @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) } } - fn assign_height(ctx: &LayoutContext) { + fn assign_height(@self, ctx: &LayoutContext) { match self { - BlockFlow(*) => self.assign_height_block(ctx), - InlineFlow(*) => self.assign_height_inline(ctx), - RootFlow(*) => self.assign_height_root(ctx), + @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) } } - fn build_display_list_recurse(builder: &dl::DisplayListBuilder, dirty: &Rect, + fn build_display_list_recurse(@self, builder: &dl::DisplayListBuilder, dirty: &Rect, offset: &Point2D, list: &dl::DisplayList) { match self { - 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), + @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) } } diff --git a/src/servo/layout/inline.rs b/src/servo/layout/inline.rs index 591cf80998f..2102af2f0f7 100644 --- a/src/servo/layout/inline.rs +++ b/src/servo/layout/inline.rs @@ -18,8 +18,6 @@ use std::arc; 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 @@ -43,17 +41,129 @@ hard to try out that alternative. type BoxRange = {start: u8, len: u8}; -// TODO: flesh out into TextRunScanner -fn build_runs_for_flow(ctx: &LayoutContext, dummy_boxes: &DVec<@RenderBox>) { - for uint::range(0, dummy_boxes.len()) |i| { - match *dummy_boxes[i] { - UnscannedTextBox(d, text) => { +// stack-allocated object for scanning an inline flow into +// TextRun-containing TextBoxes. +struct TextRunScanner { + mut in_clump: bool, + mut clump_start: uint, + mut clump_end: uint, + flow: @FlowContext, +} + +fn TextRunScanner(flow: @FlowContext) -> TextRunScanner { + TextRunScanner { + in_clump: false, + clump_start: 0, + clump_end: 0, + flow: flow, + } +} + +impl TextRunScanner { + fn scan_for_runs(ctx: &LayoutContext) { + // if reused, must be reset. + assert !self.in_clump; + let in_boxes = self.flow.inline().boxes; + assert in_boxes.len() > 0; + + debug!("scanning %u boxes for text runs...", in_boxes.len()); + + let temp_boxes = DVec(); + let mut prev_box: @RenderBox = in_boxes[0]; + + for uint::range(0, in_boxes.len()) |i| { + debug!("considering box: %?", in_boxes[i].debug_str()); + + let can_coalesce_with_prev = i > 0 && boxes_can_be_coalesced(prev_box, in_boxes[i]); + + match (self.in_clump, can_coalesce_with_prev) { + // start a new clump + (false, _) => { self.reset_clump_to_index(i); }, + // extend clump + (true, true) => { self.clump_end = i; }, + // boundary detected; flush and start new clump + (true, false) => { + self.flush_clump_to_list(ctx, &temp_boxes); + self.reset_clump_to_index(i); + } + }; + + prev_box = in_boxes[i]; + } + + // handle remaining clumps + if self.in_clump { + self.flush_clump_to_list(ctx, &temp_boxes); + } + + debug!("swapping out boxes."); + // swap out old and new box list of flow + self.flow.inline().boxes.set(dvec::unwrap(temp_boxes)); + + debug!("new inline flow boxes:"); + do self.flow.inline().boxes.each |box| { + debug!("%s", box.debug_str()); true + } + + // helper functions + pure fn boxes_can_be_coalesced(a: @RenderBox, b: @RenderBox) -> bool { + assert !core::box::ptr_eq(a, b); + + match (a, b) { + // TODO: check whether text styles, fonts are the same. + (@UnscannedTextBox(*), @UnscannedTextBox(*)) => a.can_merge_with_box(b), + (_, _) => false + } + } + } + + fn reset_clump_to_index(i: uint) { + debug!("resetting clump to %u", i); + + self.clump_start = i; + self.clump_end = i; + self.in_clump = true; + } + + fn flush_clump_to_list(ctx: &LayoutContext, temp_boxes: &DVec<@RenderBox>) { + assert self.in_clump; + + debug!("flushing when start=%?,end=%?", self.clump_start, self.clump_end); + + let in_boxes = self.flow.inline().boxes; + let is_singleton = (self.clump_start == self.clump_end); + let is_text_clump = match in_boxes[self.clump_start] { + @UnscannedTextBox(*) => true, + _ => false + }; + + // TODO: repair the mapping of DOM elements to boxes if it changed. + // (the mapping does not yet exist; see Issue #103) + match (is_singleton, is_text_clump) { + (false, false) => fail ~"WAT: can't coalesce non-text boxes in flush_clump_to_list()!", + (true, false) => { temp_boxes.push(in_boxes[self.clump_start]); } + (true, true) => { + let text = in_boxes[self.clump_start].raw_text(); + // TODO: use actual font for corresponding DOM node to create text run. let run = TextRun(&*ctx.font_cache.get_test_font(), text); let box_guts = TextBoxData(@run, 0, text.len()); - dummy_boxes.set_elt(i, @TextBox(d, box_guts)); + debug!("pushing when start=%?,end=%?", self.clump_start, self.clump_end); + temp_boxes.push(@TextBox(copy *in_boxes[self.clump_start].d(), box_guts)); }, - _ => {} + (false, true) => { + let mut run_str : ~str = ~""; + // TODO: is using ropes to construct the merged text any faster? + do uint::range(self.clump_start, self.clump_end+1) |i| { + run_str = str::append(run_str, in_boxes[i].raw_text()); true + } + // TODO: use actual font for corresponding DOM node to create text run. + let run = TextRun(&*ctx.font_cache.get_test_font(), move run_str); + let box_guts = TextBoxData(@run, 0, run.text.len()); + debug!("pushing when start=%?,end=%?", self.clump_start, self.clump_end); + temp_boxes.push(@TextBox(copy *in_boxes[self.clump_start].d(), box_guts)); + } } + self.in_clump = false; } } @@ -81,25 +191,30 @@ fn InlineFlowData() -> InlineFlowData { trait InlineLayout { pure fn starts_inline_flow() -> bool; - 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); + fn bubble_widths_inline(@self, ctx: &LayoutContext); + fn assign_widths_inline(@self, ctx: &LayoutContext); + fn assign_height_inline(@self, ctx: &LayoutContext); + fn build_display_list_inline(@self, a: &dl::DisplayListBuilder, b: &Rect, c: &Point2D, d: &dl::DisplayList); } impl FlowContext : InlineLayout { pure fn starts_inline_flow() -> bool { match self { InlineFlow(*) => true, _ => false } } - fn bubble_widths_inline(ctx: &LayoutContext) { + fn bubble_widths_inline(@self, ctx: &LayoutContext) { assert self.starts_inline_flow(); - // TODO: this is a hack - build_runs_for_flow(ctx, &self.inline().boxes); + debug!("box count: %u", self.inline().boxes.len()); + + let scanner = TextRunScanner(self); + scanner.scan_for_runs(ctx); let mut min_width = au(0); let mut pref_width = au(0); + debug!("/box count: %u", self.inline().boxes.len()); + for self.inline().boxes.each |box| { + debug!("measuring box: %s", box.debug_str()); min_width = au::max(min_width, box.get_min_width(ctx)); pref_width = au::max(pref_width, box.get_pref_width(ctx)); } @@ -111,7 +226,7 @@ impl FlowContext : InlineLayout { /* 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(ctx: &LayoutContext) { + fn assign_widths_inline(@self, ctx: &LayoutContext) { assert self.starts_inline_flow(); /* Perform inline flow with the available width. */ @@ -170,12 +285,12 @@ impl FlowContext : InlineLayout { // 'inline-block' box that created this flow. } - fn assign_height_inline(_ctx: &LayoutContext) { + fn assign_height_inline(@self, _ctx: &LayoutContext) { // Don't need to set box or ctx heights, since that is done // during inline flowing. } - fn build_display_list_inline(builder: &dl::DisplayListBuilder, dirty: &Rect, + fn build_display_list_inline(@self, builder: &dl::DisplayListBuilder, dirty: &Rect, offset: &Point2D, list: &dl::DisplayList) { assert self.starts_inline_flow(); diff --git a/src/servo/layout/root.rs b/src/servo/layout/root.rs index 64dec3d6f6a..aa7dff3a0d5 100644 --- a/src/servo/layout/root.rs +++ b/src/servo/layout/root.rs @@ -22,11 +22,12 @@ fn RootFlowData() -> RootFlowData { trait RootLayout { pure fn starts_root_flow() -> bool; - fn bubble_widths_root(ctx: &LayoutContext); - fn assign_widths_root(ctx: &LayoutContext); - fn assign_height_root(ctx: &LayoutContext); + fn bubble_widths_root(@self, ctx: &LayoutContext); + fn assign_widths_root(@self, ctx: &LayoutContext); + fn assign_height_root(@self, ctx: &LayoutContext); - fn build_display_list_root(a: &dl::DisplayListBuilder, b: &Rect, c: &Point2D, d: &dl::DisplayList); + fn build_display_list_root(@self, a: &dl::DisplayListBuilder, b: &Rect, + c: &Point2D, d: &dl::DisplayList); } impl FlowContext : RootLayout { @@ -39,25 +40,25 @@ impl FlowContext : RootLayout { } /* defer to the block algorithm */ - fn bubble_widths_root(ctx: &LayoutContext) { + fn bubble_widths_root(@self, ctx: &LayoutContext) { assert self.starts_root_flow(); self.bubble_widths_block(ctx) } - fn assign_widths_root(ctx: &LayoutContext) { + fn assign_widths_root(@self, ctx: &LayoutContext) { assert self.starts_root_flow(); self.d().position = copy ctx.screen_size; self.assign_widths_block(ctx) } - fn assign_height_root(ctx: &LayoutContext) { + fn assign_height_root(@self, ctx: &LayoutContext) { assert self.starts_root_flow(); self.assign_height_block(ctx); } - fn build_display_list_root(builder: &dl::DisplayListBuilder, dirty: &Rect, + fn build_display_list_root(@self, builder: &dl::DisplayListBuilder, dirty: &Rect, offset: &Point2D, list: &dl::DisplayList) { assert self.starts_root_flow(); diff --git a/src/servo/layout/text.rs b/src/servo/layout/text.rs index 6b6f49be3a6..095d5b6ae6f 100644 --- a/src/servo/layout/text.rs +++ b/src/servo/layout/text.rs @@ -5,7 +5,7 @@ use au::au; use geom::size::Size2D; use servo_text::text_run::TextRun; use servo_text::font_cache::FontCache; -use layout::box::{TextBox, RenderBox}; +use layout::box::{TextBox, RenderBox, UnscannedTextBox}; use layout::context::LayoutContext; pub struct TextBoxData { @@ -22,6 +22,19 @@ pub fn TextBoxData(run: @TextRun, offset: uint, length: uint) -> TextBoxData { } } +trait UnscannedMethods { + pure fn raw_text() -> ~str; +} + +impl RenderBox : UnscannedMethods { + pure fn raw_text() -> ~str { + match self { + UnscannedTextBox(_, s) => copy s, + _ => fail ~"unsupported operation: box.raw_text() on non-unscanned text box." + } + } +} + /* The main reflow routine for text layout. impl @RenderBox : TextLayout { fn reflow_text(ctx: &LayoutContext) { diff --git a/src/servo/text/glyph.rs b/src/servo/text/glyph.rs index 3691fce2f19..cc226b202b1 100644 --- a/src/servo/text/glyph.rs +++ b/src/servo/text/glyph.rs @@ -463,10 +463,10 @@ struct GlyphStore { // Initializes the glyph store, but doesn't actually shape anything. // Use the set_glyph, set_glyphs() methods to store glyph data. -fn GlyphStore(text: &str) -> GlyphStore { - assert text.len() > 0; +fn GlyphStore(length: uint) -> GlyphStore { + assert length > 0; - let buffer = vec::from_elem(text.len(), InitialGlyphEntry()); + let buffer = vec::from_elem(length, InitialGlyphEntry()); GlyphStore { entry_buffer: dvec::from_vec(buffer), diff --git a/src/servo/text/text_run.rs b/src/servo/text/text_run.rs index e89c212b4e2..ad5acc0fe57 100644 --- a/src/servo/text/text_run.rs +++ b/src/servo/text/text_run.rs @@ -26,16 +26,19 @@ impl TextRun { assert offset < self.text.len(); assert offset + length <= self.text.len(); + debug!("enter min_width_for_range(o=%?, l=%?)", offset, length); + let mut max_piece_width = au(0); // TODO: use a real font reference let font = ctx.font_cache.get_test_font(); for self.iter_indivisible_pieces_for_range(offset, length) |piece_offset, piece_len| { - let metrics = font.measure_text(&self, piece_offset, piece_len); if metrics.advance > max_piece_width { max_piece_width = metrics.advance; } }; + + debug!("exit min_width_for_range(o=%?, l=%?)", offset, length); return max_piece_width; } @@ -75,6 +78,8 @@ impl TextRun { assert offset < self.text.len(); assert offset + length <= self.text.len(); + debug!("enter iter_indivisible_pieces_for_range(o=%?, l=%?)", offset, length); + //TODO: need a more sophisticated model of words and possible breaks let text = str::view(self.text, offset, length); @@ -117,11 +122,12 @@ impl TextRun { } } } + debug!("exit iter_indivisible_pieces_for_range(o=%?, l=%?)", offset, length); } } -fn TextRun(font: &Font, +text: ~str) -> TextRun { - let glyph_store = GlyphStore(text); +fn TextRun(font: &Font, text: ~str) -> TextRun { + let glyph_store = GlyphStore(text.len()); let run = TextRun { text: text, glyphs: glyph_store,