mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +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::{Flow_Inline, Flow_InlineBlock, Flow_Root, Flow_Table, FlowContext};
|
||||||
use layout::flow::{FlowContextType, FlowData, InlineBlockFlow, InlineFlow, TableFlow};
|
use layout::flow::{FlowContextType, FlowData, InlineBlockFlow, InlineFlow, TableFlow};
|
||||||
use layout::inline::{InlineFlowData, InlineLayout};
|
use layout::inline::{InlineFlowData, InlineLayout};
|
||||||
|
use layout::text::TextRunScanner;
|
||||||
use css::node_style::StyledNode;
|
use css::node_style::StyledNode;
|
||||||
|
|
||||||
use newcss::values::{CSSDisplay, CSSDisplayBlock, CSSDisplayInline, CSSDisplayInlineBlock};
|
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
|
/// 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.
|
/// 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 {
|
match *parent_flow {
|
||||||
InlineFlow(*) => {
|
InlineFlow(*) => {
|
||||||
let mut found_child_inline = false;
|
let mut found_child_inline = false;
|
||||||
|
@ -486,7 +487,7 @@ impl LayoutTreeBuilder {
|
||||||
self.fixup_split_inline(*parent_flow)
|
self.fixup_split_inline(*parent_flow)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
BlockFlow(*) => {
|
BlockFlow(*) | FloatFlow(*) => {
|
||||||
// FIXME: this will create refcounted cycles between the removed flow and any
|
// FIXME: this will create refcounted cycles between the removed flow and any
|
||||||
// of its RenderBox or FlowContext children, and possibly keep alive other junk
|
// 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std;
|
|
||||||
use layout::box::{CannotSplit, GenericRenderBoxClass, ImageRenderBoxClass, RenderBox};
|
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::context::LayoutContext;
|
||||||
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
|
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
|
||||||
use layout::flow::{FlowContext, FlowData, InlineFlow};
|
use layout::flow::{FlowContext, FlowData, InlineFlow};
|
||||||
use layout::text::{UnscannedMethods, adapt_textbox_with_range};
|
|
||||||
use layout::float_context::FloatContext;
|
use layout::float_context::FloatContext;
|
||||||
|
use layout::util::{ElementMapping};
|
||||||
|
|
||||||
use std::u16;
|
use std::u16;
|
||||||
use std::uint;
|
|
||||||
use std::util;
|
use std::util;
|
||||||
use std::vec;
|
|
||||||
use geom::{Point2D, Rect, Size2D};
|
use geom::{Point2D, Rect, Size2D};
|
||||||
use gfx::display_list::DisplayList;
|
use gfx::display_list::DisplayList;
|
||||||
use gfx::geometry::Au;
|
use gfx::geometry::Au;
|
||||||
use gfx::text::text_run::TextRun;
|
use newcss::values::{CSSTextAlignLeft, CSSTextAlignCenter, CSSTextAlignRight, CSSTextAlignJustify};
|
||||||
use gfx::text::util::*;
|
|
||||||
use newcss::values::{CSSTextAlignCenter, CSSTextAlignJustify, CSSTextAlignLeft};
|
|
||||||
use newcss::values::{CSSTextAlignRight, CSSTextDecoration, CSSTextDecorationUnderline};
|
|
||||||
use script::dom::node::{AbstractNode, LayoutView};
|
|
||||||
use newcss::units::{Em, Px, Pt};
|
use newcss::units::{Em, Px, Pt};
|
||||||
use newcss::values::{CSSLineHeightNormal, CSSLineHeightNumber, CSSLineHeightLength, CSSLineHeightPercentage};
|
use newcss::values::{CSSLineHeightNormal, CSSLineHeightNumber, CSSLineHeightLength, CSSLineHeightPercentage};
|
||||||
|
|
||||||
use servo_util::range::Range;
|
use servo_util::range::Range;
|
||||||
use servo_util::tree::{TreeNodeRef, TreeUtils};
|
use servo_util::tree::{TreeNodeRef, TreeUtils};
|
||||||
use extra::deque::Deque;
|
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.
|
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 {
|
struct PendingLine {
|
||||||
range: Range,
|
range: Range,
|
||||||
bounds: Rect<Au>
|
bounds: Rect<Au>
|
||||||
|
@ -699,8 +347,6 @@ impl InlineLayout for FlowContext {
|
||||||
|
|
||||||
impl InlineFlowData {
|
impl InlineFlowData {
|
||||||
pub fn bubble_widths_inline(@mut self, ctx: &mut LayoutContext) {
|
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;
|
let mut num_floats = 0;
|
||||||
|
|
||||||
for InlineFlow(self).each_child |kid| {
|
for InlineFlow(self).each_child |kid| {
|
||||||
|
@ -710,7 +356,6 @@ impl InlineFlowData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let this = &mut *self;
|
let this = &mut *self;
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,17 @@
|
||||||
|
|
||||||
//! Text layout.
|
//! Text layout.
|
||||||
|
|
||||||
use layout::box::{RenderBox, RenderBoxBase, TextRenderBox, UnscannedTextRenderBoxClass};
|
use std::uint;
|
||||||
|
use std::vec;
|
||||||
|
|
||||||
use gfx::text::text_run::TextRun;
|
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;
|
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 box_builder;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod display_list_builder;
|
pub mod display_list_builder;
|
||||||
|
pub mod float_context;
|
||||||
|
pub mod float;
|
||||||
pub mod flow;
|
pub mod flow;
|
||||||
pub mod layout_task;
|
pub mod layout_task;
|
||||||
pub mod inline;
|
pub mod inline;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod float_context;
|
pub mod util;
|
||||||
pub mod float;
|
|
||||||
mod aux;
|
mod aux;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue