mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Create text runs during box building
This commit is contained in:
parent
bc520e0143
commit
4b172a312d
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