diff --git a/src/components/main/layout/box_builder.rs b/src/components/main/layout/box_builder.rs index 1720fc1d98d..07e9e6e3b3b 100644 --- a/src/components/main/layout/box_builder.rs +++ b/src/components/main/layout/box_builder.rs @@ -15,6 +15,7 @@ use layout::flow::{AbsoluteFlow, BlockFlow, FloatFlow, Flow_Absolute, Flow_Block use layout::flow::{Flow_Inline, Flow_InlineBlock, Flow_Root, Flow_Table, FlowContext}; use layout::flow::{FlowContextType, FlowData, InlineBlockFlow, InlineFlow, TableFlow}; use layout::inline::{InlineFlowData, InlineLayout}; +use layout::text::TextRunScanner; use css::node_style::StyledNode; use newcss::values::{CSSDisplay, CSSDisplayBlock, CSSDisplayInline, CSSDisplayInlineBlock}; @@ -467,7 +468,7 @@ impl LayoutTreeBuilder { /// /// The latter can only be done immediately adjacent to, or at the beginning or end of a block /// flow. Otherwise, the whitespace might affect whitespace collapsing with adjacent text. - pub fn simplify_children_of_flow(&self, _: &LayoutContext, parent_flow: &mut FlowContext) { + pub fn simplify_children_of_flow(&self, ctx: &LayoutContext, parent_flow: &mut FlowContext) { match *parent_flow { InlineFlow(*) => { let mut found_child_inline = false; @@ -486,7 +487,7 @@ impl LayoutTreeBuilder { self.fixup_split_inline(*parent_flow) } }, - BlockFlow(*) => { + BlockFlow(*) | FloatFlow(*) => { // FIXME: this will create refcounted cycles between the removed flow and any // of its RenderBox or FlowContext children, and possibly keep alive other junk @@ -536,6 +537,18 @@ impl LayoutTreeBuilder { } } } + + // Issue 543: We only need to do this if there are inline child + // flows, but there's not a quick way to check at the moment. + for (*parent_flow).each_child |child_flow: FlowContext| { + match child_flow { + InlineFlow(*) | InlineBlockFlow(*) => { + let mut scanner = TextRunScanner::new(); + scanner.scan_for_runs(ctx, child_flow); + } + _ => {} + } + } }, _ => {} } diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index f6895a98e60..cd15e3e8ee5 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -3,30 +3,22 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use std::cell::Cell; -use std; use layout::box::{CannotSplit, GenericRenderBoxClass, ImageRenderBoxClass, RenderBox}; -use layout::box::{SplitDidFit, SplitDidNotFit, TextRenderBoxClass, UnscannedTextRenderBoxClass}; +use layout::box::{SplitDidFit, SplitDidNotFit, TextRenderBoxClass}; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::flow::{FlowContext, FlowData, InlineFlow}; -use layout::text::{UnscannedMethods, adapt_textbox_with_range}; use layout::float_context::FloatContext; +use layout::util::{ElementMapping}; use std::u16; -use std::uint; use std::util; -use std::vec; use geom::{Point2D, Rect, Size2D}; use gfx::display_list::DisplayList; use gfx::geometry::Au; -use gfx::text::text_run::TextRun; -use gfx::text::util::*; -use newcss::values::{CSSTextAlignCenter, CSSTextAlignJustify, CSSTextAlignLeft}; -use newcss::values::{CSSTextAlignRight, CSSTextDecoration, CSSTextDecorationUnderline}; -use script::dom::node::{AbstractNode, LayoutView}; +use newcss::values::{CSSTextAlignLeft, CSSTextAlignCenter, CSSTextAlignRight, CSSTextAlignJustify}; use newcss::units::{Em, Px, Pt}; use newcss::values::{CSSLineHeightNormal, CSSLineHeightNumber, CSSLineHeightLength, CSSLineHeightPercentage}; - use servo_util::range::Range; use servo_util::tree::{TreeNodeRef, TreeUtils}; use extra::deque::Deque; @@ -53,350 +45,6 @@ serve as the starting point, but the current design doesn't make it hard to try out that alternative. */ -pub struct NodeRange { - node: AbstractNode, - range: Range, -} - -impl NodeRange { - pub fn new(node: AbstractNode, range: &Range) -> NodeRange { - NodeRange { node: node, range: copy *range } - } -} - -struct ElementMapping { - priv entries: ~[NodeRange], -} - -impl ElementMapping { - pub fn new() -> ElementMapping { - ElementMapping { entries: ~[] } - } - - pub fn add_mapping(&mut self, node: AbstractNode, range: &Range) { - self.entries.push(NodeRange::new(node, range)) - } - - pub fn each(&self, callback: &fn(nr: &NodeRange) -> bool) -> bool { - for self.entries.each |nr| { - if !callback(nr) { - break - } - } - true - } - - pub fn eachi(&self, callback: &fn(i: uint, nr: &NodeRange) -> bool) -> bool { - for self.entries.eachi |i, nr| { - if !callback(i, nr) { - break - } - } - true - } - - pub fn eachi_mut(&self, callback: &fn(i: uint, nr: &NodeRange) -> bool) -> bool { - for self.entries.eachi |i, nr| { - if !callback(i, nr) { - break - } - } - true - } - - pub fn repair_for_box_changes(&mut self, old_boxes: &[RenderBox], new_boxes: &[RenderBox]) { - let entries = &mut self.entries; - - debug!("--- Old boxes: ---"); - for old_boxes.eachi |i, box| { - debug!("%u --> %s", i, box.debug_str()); - } - debug!("------------------"); - - debug!("--- New boxes: ---"); - for new_boxes.eachi |i, box| { - debug!("%u --> %s", i, box.debug_str()); - } - debug!("------------------"); - - debug!("--- Elem ranges before repair: ---"); - for entries.eachi |i: uint, nr: &NodeRange| { - debug!("%u: %? --> %s", i, nr.range, nr.node.debug_str()); - } - debug!("----------------------------------"); - - let mut old_i = 0; - let mut new_j = 0; - - struct WorkItem { - begin_idx: uint, - entry_idx: uint, - }; - let mut repair_stack : ~[WorkItem] = ~[]; - - // index into entries - let mut entries_k = 0; - - while old_i < old_boxes.len() { - debug!("repair_for_box_changes: Considering old box %u", old_i); - // possibly push several items - while entries_k < entries.len() && old_i == entries[entries_k].range.begin() { - let item = WorkItem {begin_idx: new_j, entry_idx: entries_k}; - debug!("repair_for_box_changes: Push work item for elem %u: %?", entries_k, item); - repair_stack.push(item); - entries_k += 1; - } - // XXX: the following loop form causes segfaults; assigning to locals doesn't. - // while new_j < new_boxes.len() && old_boxes[old_i].d().node != new_boxes[new_j].d().node { - while new_j < new_boxes.len() { - let should_leave = do old_boxes[old_i].with_base |old_box_base| { - do new_boxes[new_j].with_base |new_box_base| { - old_box_base.node != new_box_base.node - } - }; - if should_leave { - break - } - - debug!("repair_for_box_changes: Slide through new box %u", new_j); - new_j += 1; - } - - old_i += 1; - - // possibly pop several items - while repair_stack.len() > 0 && old_i == entries[repair_stack.last().entry_idx].range.end() { - let item = repair_stack.pop(); - debug!("repair_for_box_changes: Set range for %u to %?", - item.entry_idx, Range::new(item.begin_idx, new_j - item.begin_idx)); - entries[item.entry_idx].range = Range::new(item.begin_idx, new_j - item.begin_idx); - } - } - debug!("--- Elem ranges after repair: ---"); - for entries.eachi |i: uint, nr: &NodeRange| { - debug!("%u: %? --> %s", i, nr.range, nr.node.debug_str()); - } - debug!("----------------------------------"); - } -} - -/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextBox`es. -struct TextRunScanner { - clump: Range, -} - -impl TextRunScanner { - fn new() -> TextRunScanner { - TextRunScanner { - clump: Range::empty(), - } - } -} - -impl TextRunScanner { - fn scan_for_runs(&mut self, ctx: &mut LayoutContext, flow: FlowContext) { - let inline = flow.inline(); - assert!(inline.boxes.len() > 0); - debug!("TextRunScanner: scanning %u boxes for text runs...", inline.boxes.len()); - - let mut last_whitespace = true; - let mut out_boxes = ~[]; - for uint::range(0, flow.inline().boxes.len()) |box_i| { - debug!("TextRunScanner: considering box: %?", flow.inline().boxes[box_i].debug_str()); - if box_i > 0 && !can_coalesce_text_nodes(flow.inline().boxes, box_i-1, box_i) { - last_whitespace = self.flush_clump_to_list(ctx, flow, last_whitespace, &mut out_boxes); - } - self.clump.extend_by(1); - } - // handle remaining clumps - if self.clump.length() > 0 { - self.flush_clump_to_list(ctx, flow, last_whitespace, &mut out_boxes); - } - - debug!("TextRunScanner: swapping out boxes."); - - // Swap out the old and new box list of the flow. - flow.inline().boxes = out_boxes; - - // A helper function. - fn can_coalesce_text_nodes(boxes: &[RenderBox], left_i: uint, right_i: uint) -> bool { - assert!(left_i < boxes.len()); - assert!(right_i > 0 && right_i < boxes.len()); - assert!(left_i != right_i); - - let (left, right) = (boxes[left_i], boxes[right_i]); - match (left, right) { - (UnscannedTextRenderBoxClass(*), UnscannedTextRenderBoxClass(*)) => { - left.can_merge_with_box(right) - } - (_, _) => false - } - } - } - - /// A "clump" is a range of inline flow leaves that can be merged together into a single - /// `RenderBox`. Adjacent text with the same style can be merged, and nothing else can. - /// - /// The flow keeps track of the `RenderBox`es contained by all non-leaf DOM nodes. This is - /// necessary for correct painting order. Since we compress several leaf `RenderBox`es here, - /// the mapping must be adjusted. - /// - /// N.B. `in_boxes` is passed by reference, since the old code used a `DVec`. The caller is - /// responsible for swapping out the list. It is not clear to me (pcwalton) that this is still - /// necessary. - fn flush_clump_to_list(&mut self, - ctx: &mut LayoutContext, - flow: FlowContext, - last_whitespace: bool, - out_boxes: &mut ~[RenderBox]) -> bool { - let inline = &mut *flow.inline(); - let in_boxes = &inline.boxes; - - fn has_underline(decoration: CSSTextDecoration) -> bool{ - match decoration { - CSSTextDecorationUnderline => true, - _ => false - } - } - - assert!(self.clump.length() > 0); - - debug!("TextRunScanner: flushing boxes in range=%?", self.clump); - let is_singleton = self.clump.length() == 1; - let is_text_clump = match in_boxes[self.clump.begin()] { - UnscannedTextRenderBoxClass(*) => true, - _ => false - }; - - let mut new_whitespace = last_whitespace; - - match (is_singleton, is_text_clump) { - (false, false) => { - fail!(~"WAT: can't coalesce non-text nodes in flush_clump_to_list()!") - } - (true, false) => { - debug!("TextRunScanner: pushing single non-text box in range: %?", self.clump); - out_boxes.push(in_boxes[self.clump.begin()]); - }, - (true, true) => { - let old_box = in_boxes[self.clump.begin()]; - let text = old_box.raw_text(); - let font_style = old_box.font_style(); - let underline = has_underline(old_box.text_decoration()); - - // TODO(#115): Use the actual CSS `white-space` property of the relevant style. - let compression = CompressWhitespaceNewline; - - let (transformed_text, whitespace) = transform_text(text, compression, last_whitespace); - new_whitespace = whitespace; - - if transformed_text.len() > 0 { - // TODO(#177): Text run creation must account for the renderability of text by - // font group fonts. This is probably achieved by creating the font group above - // and then letting `FontGroup` decide which `Font` to stick into the text run. - let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&font_style); - let run = @fontgroup.create_textrun(transformed_text, underline); - - debug!("TextRunScanner: pushing single text box in range: %?", self.clump); - let new_box = do old_box.with_base |old_box_base| { - let range = Range::new(0, run.char_len()); - @mut adapt_textbox_with_range(*old_box_base, run, range) - }; - - out_boxes.push(TextRenderBoxClass(new_box)); - } - }, - (false, true) => { - // TODO(#115): Use the actual CSS `white-space` property of the relevant style. - let compression = CompressWhitespaceNewline; - - // First, transform/compress text of all the nodes. - let mut last_whitespace = true; - let transformed_strs: ~[~str] = do vec::from_fn(self.clump.length()) |i| { - // TODO(#113): We should be passing the compression context between calls to - // `transform_text`, so that boxes starting and/or ending with whitespace can - // be compressed correctly with respect to the text run. - let idx = i + self.clump.begin(); - let (new_str, new_whitespace) = transform_text(in_boxes[idx].raw_text(), compression, last_whitespace); - last_whitespace = new_whitespace; - new_str - }; - new_whitespace = last_whitespace; - - // Next, concatenate all of the transformed strings together, saving the new - // character indices. - let mut run_str: ~str = ~""; - let mut new_ranges: ~[Range] = ~[]; - let mut char_total = 0; - for uint::range(0, transformed_strs.len()) |i| { - let added_chars = transformed_strs[i].char_len(); - new_ranges.push(Range::new(char_total, added_chars)); - run_str.push_str(transformed_strs[i]); - char_total += added_chars; - } - - // Now create the run. - // - // TODO(#177): Text run creation must account for the renderability of text by - // font group fonts. This is probably achieved by creating the font group above - // and then letting `FontGroup` decide which `Font` to stick into the text run. - let font_style = in_boxes[self.clump.begin()].font_style(); - let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&font_style); - let underline = has_underline(in_boxes[self.clump.begin()].text_decoration()); - - // TextRuns contain a cycle which is usually resolved by the teardown - // sequence. If no clump takes ownership, however, it will leak. - let clump = self.clump; - let run = if clump.length() != 0 && run_str.len() > 0 { - Some(@TextRun::new(fontgroup.fonts[0], run_str, underline)) - } else { - None - }; - - // Make new boxes with the run and adjusted text indices. - debug!("TextRunScanner: pushing box(es) in range: %?", self.clump); - for clump.eachi |i| { - let range = new_ranges[i - self.clump.begin()]; - if range.length() == 0 { - error!("Elided an `UnscannedTextbox` because it was zero-length after \ - compression; %s", - in_boxes[i].debug_str()); - loop - } - - do in_boxes[i].with_base |base| { - let new_box = @mut adapt_textbox_with_range(*base, run.get(), range); - out_boxes.push(TextRenderBoxClass(new_box)); - } - } - } - } // End of match. - - debug!("--- In boxes: ---"); - for in_boxes.eachi |i, box| { - debug!("%u --> %s", i, box.debug_str()); - } - debug!("------------------"); - - debug!("--- Out boxes: ---"); - for out_boxes.eachi |i, box| { - debug!("%u --> %s", i, box.debug_str()); - } - debug!("------------------"); - - debug!("--- Elem ranges: ---"); - for inline.elems.eachi_mut |i: uint, nr: &NodeRange| { - debug!("%u: %? --> %s", i, nr.range, nr.node.debug_str()); () - } - debug!("--------------------"); - - let end = self.clump.end(); // FIXME: borrow checker workaround - self.clump.reset(end, 0); - - new_whitespace - } // End of `flush_clump_to_list`. -} - struct PendingLine { range: Range, bounds: Rect @@ -699,8 +347,6 @@ impl InlineLayout for FlowContext { impl InlineFlowData { pub fn bubble_widths_inline(@mut self, ctx: &mut LayoutContext) { - let mut scanner = TextRunScanner::new(); - scanner.scan_for_runs(ctx, InlineFlow(self)); let mut num_floats = 0; for InlineFlow(self).each_child |kid| { @@ -710,7 +356,6 @@ impl InlineFlowData { } } - { let this = &mut *self; diff --git a/src/components/main/layout/text.rs b/src/components/main/layout/text.rs index 57f0a7db870..e335fac37a5 100644 --- a/src/components/main/layout/text.rs +++ b/src/components/main/layout/text.rs @@ -4,8 +4,17 @@ //! Text layout. -use layout::box::{RenderBox, RenderBoxBase, TextRenderBox, UnscannedTextRenderBoxClass}; +use std::uint; +use std::vec; + use gfx::text::text_run::TextRun; +use gfx::text::util::{CompressWhitespaceNewline, transform_text}; +use layout::box::{RenderBox, RenderBoxBase, TextRenderBox}; +use layout::box::{TextRenderBoxClass, UnscannedTextRenderBoxClass}; +use layout::context::LayoutContext; +use layout::flow::FlowContext; +use layout::util::{NodeRange}; +use newcss::values::{CSSTextDecoration, CSSTextDecorationUnderline}; use servo_util::range::Range; @@ -45,3 +54,220 @@ impl UnscannedMethods for RenderBox { } } } + +/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextBox`es. +struct TextRunScanner { + clump: Range, +} + +impl TextRunScanner { + pub fn new() -> TextRunScanner { + TextRunScanner { + clump: Range::empty(), + } + } + + pub fn scan_for_runs(&mut self, ctx: &LayoutContext, flow: FlowContext) { + let inline = flow.inline(); + assert!(inline.boxes.len() > 0); + debug!("TextRunScanner: scanning %u boxes for text runs...", inline.boxes.len()); + + let mut last_whitespace = true; + let mut out_boxes = ~[]; + for uint::range(0, flow.inline().boxes.len()) |box_i| { + debug!("TextRunScanner: considering box: %?", flow.inline().boxes[box_i].debug_str()); + if box_i > 0 && !can_coalesce_text_nodes(flow.inline().boxes, box_i-1, box_i) { + last_whitespace = self.flush_clump_to_list(ctx, flow, last_whitespace, &mut out_boxes); + } + self.clump.extend_by(1); + } + // handle remaining clumps + if self.clump.length() > 0 { + self.flush_clump_to_list(ctx, flow, last_whitespace, &mut out_boxes); + } + + debug!("TextRunScanner: swapping out boxes."); + + // Swap out the old and new box list of the flow. + flow.inline().boxes = out_boxes; + + // A helper function. + fn can_coalesce_text_nodes(boxes: &[RenderBox], left_i: uint, right_i: uint) -> bool { + assert!(left_i < boxes.len()); + assert!(right_i > 0 && right_i < boxes.len()); + assert!(left_i != right_i); + + let (left, right) = (boxes[left_i], boxes[right_i]); + match (left, right) { + (UnscannedTextRenderBoxClass(*), UnscannedTextRenderBoxClass(*)) => { + left.can_merge_with_box(right) + } + (_, _) => false + } + } + } + + /// A "clump" is a range of inline flow leaves that can be merged together into a single + /// `RenderBox`. Adjacent text with the same style can be merged, and nothing else can. + /// + /// The flow keeps track of the `RenderBox`es contained by all non-leaf DOM nodes. This is + /// necessary for correct painting order. Since we compress several leaf `RenderBox`es here, + /// the mapping must be adjusted. + /// + /// N.B. `in_boxes` is passed by reference, since the old code used a `DVec`. The caller is + /// responsible for swapping out the list. It is not clear to me (pcwalton) that this is still + /// necessary. + pub fn flush_clump_to_list(&mut self, + ctx: &LayoutContext, + flow: FlowContext, + last_whitespace: bool, + out_boxes: &mut ~[RenderBox]) -> bool { + let inline = &mut *flow.inline(); + let in_boxes = &inline.boxes; + + fn has_underline(decoration: CSSTextDecoration) -> bool{ + match decoration { + CSSTextDecorationUnderline => true, + _ => false + } + } + + assert!(self.clump.length() > 0); + + debug!("TextRunScanner: flushing boxes in range=%?", self.clump); + let is_singleton = self.clump.length() == 1; + let is_text_clump = match in_boxes[self.clump.begin()] { + UnscannedTextRenderBoxClass(*) => true, + _ => false + }; + + let mut new_whitespace = last_whitespace; + + match (is_singleton, is_text_clump) { + (false, false) => { + fail!(~"WAT: can't coalesce non-text nodes in flush_clump_to_list()!") + } + (true, false) => { + debug!("TextRunScanner: pushing single non-text box in range: %?", self.clump); + out_boxes.push(in_boxes[self.clump.begin()]); + }, + (true, true) => { + let old_box = in_boxes[self.clump.begin()]; + let text = old_box.raw_text(); + let font_style = old_box.font_style(); + let underline = has_underline(old_box.text_decoration()); + + // TODO(#115): Use the actual CSS `white-space` property of the relevant style. + let compression = CompressWhitespaceNewline; + + let (transformed_text, whitespace) = transform_text(text, compression, last_whitespace); + new_whitespace = whitespace; + + if transformed_text.len() > 0 { + // TODO(#177): Text run creation must account for the renderability of text by + // font group fonts. This is probably achieved by creating the font group above + // and then letting `FontGroup` decide which `Font` to stick into the text run. + let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&font_style); + let run = @fontgroup.create_textrun(transformed_text, underline); + + debug!("TextRunScanner: pushing single text box in range: %?", self.clump); + let new_box = do old_box.with_base |old_box_base| { + let range = Range::new(0, run.char_len()); + @mut adapt_textbox_with_range(*old_box_base, run, range) + }; + + out_boxes.push(TextRenderBoxClass(new_box)); + } + }, + (false, true) => { + // TODO(#115): Use the actual CSS `white-space` property of the relevant style. + let compression = CompressWhitespaceNewline; + + // First, transform/compress text of all the nodes. + let mut last_whitespace_in_clump = new_whitespace; + let transformed_strs: ~[~str] = do vec::from_fn(self.clump.length()) |i| { + // TODO(#113): We should be passing the compression context between calls to + // `transform_text`, so that boxes starting and/or ending with whitespace can + // be compressed correctly with respect to the text run. + let idx = i + self.clump.begin(); + let (new_str, new_whitespace) = transform_text(in_boxes[idx].raw_text(), + compression, + last_whitespace_in_clump); + last_whitespace_in_clump = new_whitespace; + new_str + }; + new_whitespace = last_whitespace_in_clump; + + // Next, concatenate all of the transformed strings together, saving the new + // character indices. + let mut run_str: ~str = ~""; + let mut new_ranges: ~[Range] = ~[]; + let mut char_total = 0; + for uint::range(0, transformed_strs.len()) |i| { + let added_chars = transformed_strs[i].char_len(); + new_ranges.push(Range::new(char_total, added_chars)); + run_str.push_str(transformed_strs[i]); + char_total += added_chars; + } + + // Now create the run. + // + // TODO(#177): Text run creation must account for the renderability of text by + // font group fonts. This is probably achieved by creating the font group above + // and then letting `FontGroup` decide which `Font` to stick into the text run. + let font_style = in_boxes[self.clump.begin()].font_style(); + let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&font_style); + let underline = has_underline(in_boxes[self.clump.begin()].text_decoration()); + + // TextRuns contain a cycle which is usually resolved by the teardown + // sequence. If no clump takes ownership, however, it will leak. + let clump = self.clump; + let run = if clump.length() != 0 && run_str.len() > 0 { + Some(@TextRun::new(fontgroup.fonts[0], run_str, underline)) + } else { + None + }; + + // Make new boxes with the run and adjusted text indices. + debug!("TextRunScanner: pushing box(es) in range: %?", self.clump); + for clump.eachi |i| { + let range = new_ranges[i - self.clump.begin()]; + if range.length() == 0 { + error!("Elided an `UnscannedTextbox` because it was zero-length after \ + compression; %s", + in_boxes[i].debug_str()); + loop + } + + do in_boxes[i].with_base |base| { + let new_box = @mut adapt_textbox_with_range(*base, run.get(), range); + out_boxes.push(TextRenderBoxClass(new_box)); + } + } + } + } // End of match. + + debug!("--- In boxes: ---"); + for in_boxes.eachi |i, box| { + debug!("%u --> %s", i, box.debug_str()); + } + debug!("------------------"); + + debug!("--- Out boxes: ---"); + for out_boxes.eachi |i, box| { + debug!("%u --> %s", i, box.debug_str()); + } + debug!("------------------"); + + debug!("--- Elem ranges: ---"); + for inline.elems.eachi_mut |i: uint, nr: &NodeRange| { + debug!("%u: %? --> %s", i, nr.range, nr.node.debug_str()); () + } + debug!("--------------------"); + + let end = self.clump.end(); // FIXME: borrow checker workaround + self.clump.reset(end, 0); + + new_whitespace + } // End of `flush_clump_to_list`. +} diff --git a/src/components/main/layout/util.rs b/src/components/main/layout/util.rs new file mode 100644 index 00000000000..cc3d0267b7d --- /dev/null +++ b/src/components/main/layout/util.rs @@ -0,0 +1,134 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use layout::box::{RenderBox}; +use script::dom::node::{AbstractNode, LayoutView}; +use servo_util::range::Range; + +pub struct NodeRange { + node: AbstractNode, + range: Range, +} + +impl NodeRange { + pub fn new(node: AbstractNode, range: &Range) -> NodeRange { + NodeRange { node: node, range: copy *range } + } +} + +struct ElementMapping { + priv entries: ~[NodeRange], +} + +impl ElementMapping { + pub fn new() -> ElementMapping { + ElementMapping { entries: ~[] } + } + + pub fn add_mapping(&mut self, node: AbstractNode, range: &Range) { + self.entries.push(NodeRange::new(node, range)) + } + + pub fn each(&self, callback: &fn(nr: &NodeRange) -> bool) -> bool { + for self.entries.each |nr| { + if !callback(nr) { + break + } + } + true + } + + pub fn eachi(&self, callback: &fn(i: uint, nr: &NodeRange) -> bool) -> bool { + for self.entries.eachi |i, nr| { + if !callback(i, nr) { + break + } + } + true + } + + pub fn eachi_mut(&self, callback: &fn(i: uint, nr: &NodeRange) -> bool) -> bool { + for self.entries.eachi |i, nr| { + if !callback(i, nr) { + break + } + } + true + } + + pub fn repair_for_box_changes(&mut self, old_boxes: &[RenderBox], new_boxes: &[RenderBox]) { + let entries = &mut self.entries; + + debug!("--- Old boxes: ---"); + for old_boxes.eachi |i, box| { + debug!("%u --> %s", i, box.debug_str()); + } + debug!("------------------"); + + debug!("--- New boxes: ---"); + for new_boxes.eachi |i, box| { + debug!("%u --> %s", i, box.debug_str()); + } + debug!("------------------"); + + debug!("--- Elem ranges before repair: ---"); + for entries.eachi |i: uint, nr: &NodeRange| { + debug!("%u: %? --> %s", i, nr.range, nr.node.debug_str()); + } + debug!("----------------------------------"); + + let mut old_i = 0; + let mut new_j = 0; + + struct WorkItem { + begin_idx: uint, + entry_idx: uint, + }; + let mut repair_stack : ~[WorkItem] = ~[]; + + // index into entries + let mut entries_k = 0; + + while old_i < old_boxes.len() { + debug!("repair_for_box_changes: Considering old box %u", old_i); + // possibly push several items + while entries_k < entries.len() && old_i == entries[entries_k].range.begin() { + let item = WorkItem {begin_idx: new_j, entry_idx: entries_k}; + debug!("repair_for_box_changes: Push work item for elem %u: %?", entries_k, item); + repair_stack.push(item); + entries_k += 1; + } + // XXX: the following loop form causes segfaults; assigning to locals doesn't. + // while new_j < new_boxes.len() && old_boxes[old_i].d().node != new_boxes[new_j].d().node { + while new_j < new_boxes.len() { + let should_leave = do old_boxes[old_i].with_base |old_box_base| { + do new_boxes[new_j].with_base |new_box_base| { + old_box_base.node != new_box_base.node + } + }; + if should_leave { + break + } + + debug!("repair_for_box_changes: Slide through new box %u", new_j); + new_j += 1; + } + + old_i += 1; + + // possibly pop several items + while repair_stack.len() > 0 && old_i == entries[repair_stack.last().entry_idx].range.end() { + let item = repair_stack.pop(); + debug!("repair_for_box_changes: Set range for %u to %?", + item.entry_idx, Range::new(item.begin_idx, new_j - item.begin_idx)); + entries[item.entry_idx].range = Range::new(item.begin_idx, new_j - item.begin_idx); + } + } + debug!("--- Elem ranges after repair: ---"); + for entries.eachi |i: uint, nr: &NodeRange| { + debug!("%u: %? --> %s", i, nr.range, nr.node.debug_str()); + } + debug!("----------------------------------"); + } +} diff --git a/src/components/main/servo.rc b/src/components/main/servo.rc index d64ba408bd1..d2b3ae08c41 100755 --- a/src/components/main/servo.rc +++ b/src/components/main/servo.rc @@ -70,13 +70,14 @@ pub mod layout { pub mod box_builder; pub mod context; pub mod display_list_builder; + pub mod float_context; + pub mod float; pub mod flow; pub mod layout_task; pub mod inline; pub mod model; pub mod text; - pub mod float_context; - pub mod float; + pub mod util; mod aux; }