mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
auto merge of #539 : sfowler/servo/create-textruns-in-box-builder, r=pcwalton
This is a preliminary step to adding parallel text shaping. In order to have enough time to do a useful amount of work in parallel, we need to trigger text shaping earlier. This PR moves TextRun creation into the box building phase of layout. I'm pushing this in before the rest of the text shaping patches to avoid bitrot; it's already been through one unpleasant rebase.
This commit is contained in:
commit
a066ce67d7
5 changed files with 382 additions and 363 deletions
|
@ -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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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`.
|
||||
}
|
||||
|
|
134
src/components/main/layout/util.rs
Normal file
134
src/components/main/layout/util.rs
Normal 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!("----------------------------------");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue