Create text runs during box building

This commit is contained in:
Seth Fowler 2013-06-26 16:06:21 -07:00
parent bc520e0143
commit 4b172a312d
5 changed files with 382 additions and 363 deletions

View file

@ -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);
}
_ => {}
}
}
},
_ => {}
}

View file

@ -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<LayoutView>,
range: Range,
}
impl NodeRange {
pub fn new(node: AbstractNode<LayoutView>, 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<LayoutView>, 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<Au>
@ -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;

View file

@ -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`.
}

View file

@ -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<LayoutView>,
range: Range,
}
impl NodeRange {
pub fn new(node: AbstractNode<LayoutView>, 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<LayoutView>, 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!("----------------------------------");
}
}

View file

@ -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;
}