Implement TextRunScanner; add explicit @self to all flow-related functions.

This commit is contained in:
Brian J. Burg 2012-10-09 17:39:18 -07:00
parent 2bc9e66257
commit 26d5cfb509
9 changed files with 221 additions and 67 deletions

View file

@ -24,11 +24,11 @@ trait BlockLayout {
pure fn starts_block_flow() -> bool;
pure fn with_block_box(fn(box: &@RenderBox) -> ()) -> ();
fn bubble_widths_block(ctx: &LayoutContext);
fn assign_widths_block(ctx: &LayoutContext);
fn assign_height_block(ctx: &LayoutContext);
fn bubble_widths_block(@self, ctx: &LayoutContext);
fn assign_widths_block(@self, ctx: &LayoutContext);
fn assign_height_block(@self, ctx: &LayoutContext);
fn build_display_list_block(a: &dl::DisplayListBuilder, b: &Rect<au>,
fn build_display_list_block(@self, a: &dl::DisplayListBuilder, b: &Rect<au>,
c: &Point2D<au>, d: &dl::DisplayList);
}
@ -66,14 +66,14 @@ impl FlowContext : BlockLayout {
/* TODO: floats */
/* TODO: absolute contexts */
/* TODO: inline-blocks */
fn bubble_widths_block(ctx: &LayoutContext) {
fn bubble_widths_block(@self, ctx: &LayoutContext) {
assert self.starts_block_flow();
let mut min_width = au(0);
let mut pref_width = au(0);
/* find max width from child block contexts */
for FlowTree.each_child(@self) |child_ctx| {
for FlowTree.each_child(self) |child_ctx| {
assert child_ctx.starts_block_flow() || child_ctx.starts_inline_flow();
min_width = au::max(min_width, child_ctx.d().min_width);
@ -98,7 +98,7 @@ impl FlowContext : BlockLayout {
Dual boxes consume some width first, and the remainder is assigned to
all child (block) contexts. */
fn assign_widths_block(_ctx: &LayoutContext) {
fn assign_widths_block(@self, _ctx: &LayoutContext) {
assert self.starts_block_flow();
let mut remaining_width = self.d().position.size.width;
@ -113,19 +113,19 @@ impl FlowContext : BlockLayout {
remaining_width = remaining_width.sub(&left_used.add(&right_used));
}
for FlowTree.each_child(@self) |child_ctx| {
for FlowTree.each_child(self) |child_ctx| {
assert child_ctx.starts_block_flow() || child_ctx.starts_inline_flow();
child_ctx.d().position.origin.x = left_used;
child_ctx.d().position.size.width = remaining_width;
}
}
fn assign_height_block(_ctx: &LayoutContext) {
fn assign_height_block(@self, _ctx: &LayoutContext) {
assert self.starts_block_flow();
let mut cur_y = au(0);
for FlowTree.each_child(@self) |child_ctx| {
for FlowTree.each_child(self) |child_ctx| {
child_ctx.d().position.origin.y = cur_y;
cur_y = cur_y.add(&child_ctx.d().position.size.height);
}
@ -142,7 +142,7 @@ impl FlowContext : BlockLayout {
}
}
fn build_display_list_block(builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
fn build_display_list_block(@self, builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
offset: &Point2D<au>, list: &dl::DisplayList) {
assert self.starts_block_flow();
@ -155,7 +155,7 @@ impl FlowContext : BlockLayout {
// TODO: handle any out-of-flow elements
// go deeper into the flow tree
for FlowTree.each_child(@self) |child| {
for FlowTree.each_child(self) |child| {
self.build_display_list_for_child(builder, child, dirty, offset, list)
}
}

View file

@ -87,7 +87,7 @@ struct RenderBoxData {
enum RenderBoxType {
RenderBox_Generic,
RenderBox_Image,
RenderBox_Text
RenderBox_Text,
}
pub enum RenderBox {
@ -101,6 +101,8 @@ trait RenderBoxMethods {
pure fn d(&self) -> &self/RenderBoxData;
pure fn is_replaced() -> bool;
pure fn can_split() -> bool;
pure fn can_merge_with_box(@self, other: @RenderBox) -> bool;
pure fn content_box() -> Rect<au>;
pure fn border_box() -> Rect<au>;
@ -141,6 +143,23 @@ impl RenderBox : RenderBoxMethods {
}
}
pure fn can_split() -> bool {
match self {
TextBox(*) => true,
_ => false
}
}
pure fn can_merge_with_box(@self, other: @RenderBox) -> bool {
assert !core::box::ptr_eq(self, other);
match (self, other) {
(@UnscannedTextBox(*), @UnscannedTextBox(*)) => true,
(@TextBox(_,d1), @TextBox(_,d2)) => { core::box::ptr_eq(d1.run, d2.run) }
(_, _) => false
}
}
/** In general, these functions are transitively impure because they
* may cause glyphs to be allocated. For now, it's impure because of
* holder.get_image()

View file

@ -32,19 +32,19 @@ pub struct DisplayListBuilder {
trait FlowDisplayListBuilderMethods {
fn build_display_list(a: &DisplayListBuilder, b: &Rect<au>, c: &dl::DisplayList);
fn build_display_list(@self, a: &DisplayListBuilder, b: &Rect<au>, c: &dl::DisplayList);
fn build_display_list_for_child(a: &DisplayListBuilder, b: @FlowContext,
fn build_display_list_for_child(@self, a: &DisplayListBuilder, b: @FlowContext,
c: &Rect<au>, d: &Point2D<au>, e: &dl::DisplayList);
}
impl FlowContext: FlowDisplayListBuilderMethods {
fn build_display_list(builder: &DisplayListBuilder, dirty: &Rect<au>, list: &dl::DisplayList) {
fn build_display_list(@self, builder: &DisplayListBuilder, dirty: &Rect<au>, list: &dl::DisplayList) {
let zero = au::zero_point();
self.build_display_list_recurse(builder, dirty, &zero, list);
}
fn build_display_list_for_child(builder: &DisplayListBuilder, child: @FlowContext,
fn build_display_list_for_child(@self, builder: &DisplayListBuilder, child: @FlowContext,
dirty: &Rect<au>, offset: &Point2D<au>,
list: &dl::DisplayList) {

View file

@ -130,39 +130,39 @@ fn FlowData(id: int) -> FlowData {
/* Flow context disambiguation methods: the verbose alternative to virtual methods */
impl FlowContext {
fn bubble_widths(ctx: &LayoutContext) {
fn bubble_widths(@self, ctx: &LayoutContext) {
match self {
BlockFlow(*) => self.bubble_widths_block(ctx),
InlineFlow(*) => self.bubble_widths_inline(ctx),
RootFlow(*) => self.bubble_widths_root(ctx),
@BlockFlow(*) => self.bubble_widths_block(ctx),
@InlineFlow(*) => self.bubble_widths_inline(ctx),
@RootFlow(*) => self.bubble_widths_root(ctx),
_ => fail fmt!("Tried to bubble_widths of flow: %?", self)
}
}
fn assign_widths(ctx: &LayoutContext) {
fn assign_widths(@self, ctx: &LayoutContext) {
match self {
BlockFlow(*) => self.assign_widths_block(ctx),
InlineFlow(*) => self.assign_widths_inline(ctx),
RootFlow(*) => self.assign_widths_root(ctx),
@BlockFlow(*) => self.assign_widths_block(ctx),
@InlineFlow(*) => self.assign_widths_inline(ctx),
@RootFlow(*) => self.assign_widths_root(ctx),
_ => fail fmt!("Tried to assign_widths of flow: %?", self)
}
}
fn assign_height(ctx: &LayoutContext) {
fn assign_height(@self, ctx: &LayoutContext) {
match self {
BlockFlow(*) => self.assign_height_block(ctx),
InlineFlow(*) => self.assign_height_inline(ctx),
RootFlow(*) => self.assign_height_root(ctx),
@BlockFlow(*) => self.assign_height_block(ctx),
@InlineFlow(*) => self.assign_height_inline(ctx),
@RootFlow(*) => self.assign_height_root(ctx),
_ => fail fmt!("Tried to assign_height of flow: %?", self)
}
}
fn build_display_list_recurse(builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
fn build_display_list_recurse(@self, builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
offset: &Point2D<au>, list: &dl::DisplayList) {
match self {
RootFlow(*) => self.build_display_list_root(builder, dirty, offset, list),
BlockFlow(*) => self.build_display_list_block(builder, dirty, offset, list),
InlineFlow(*) => self.build_display_list_inline(builder, dirty, offset, list),
@RootFlow(*) => self.build_display_list_root(builder, dirty, offset, list),
@BlockFlow(*) => self.build_display_list_block(builder, dirty, offset, list),
@InlineFlow(*) => self.build_display_list_inline(builder, dirty, offset, list),
_ => fail fmt!("Tried to build_display_list_recurse of flow: %?", self)
}
}

View file

@ -18,8 +18,6 @@ use std::arc;
use util::tree;
/*
Tentative design: (may not line up with reality)
Lineboxes are represented as offsets into the child list, rather than
as an object that "owns" boxes. Choosing a different set of line
breaks requires a new list of offsets, and possibly some splitting and
@ -43,17 +41,129 @@ hard to try out that alternative.
type BoxRange = {start: u8, len: u8};
// TODO: flesh out into TextRunScanner
fn build_runs_for_flow(ctx: &LayoutContext, dummy_boxes: &DVec<@RenderBox>) {
for uint::range(0, dummy_boxes.len()) |i| {
match *dummy_boxes[i] {
UnscannedTextBox(d, text) => {
// stack-allocated object for scanning an inline flow into
// TextRun-containing TextBoxes.
struct TextRunScanner {
mut in_clump: bool,
mut clump_start: uint,
mut clump_end: uint,
flow: @FlowContext,
}
fn TextRunScanner(flow: @FlowContext) -> TextRunScanner {
TextRunScanner {
in_clump: false,
clump_start: 0,
clump_end: 0,
flow: flow,
}
}
impl TextRunScanner {
fn scan_for_runs(ctx: &LayoutContext) {
// if reused, must be reset.
assert !self.in_clump;
let in_boxes = self.flow.inline().boxes;
assert in_boxes.len() > 0;
debug!("scanning %u boxes for text runs...", in_boxes.len());
let temp_boxes = DVec();
let mut prev_box: @RenderBox = in_boxes[0];
for uint::range(0, in_boxes.len()) |i| {
debug!("considering box: %?", in_boxes[i].debug_str());
let can_coalesce_with_prev = i > 0 && boxes_can_be_coalesced(prev_box, in_boxes[i]);
match (self.in_clump, can_coalesce_with_prev) {
// start a new clump
(false, _) => { self.reset_clump_to_index(i); },
// extend clump
(true, true) => { self.clump_end = i; },
// boundary detected; flush and start new clump
(true, false) => {
self.flush_clump_to_list(ctx, &temp_boxes);
self.reset_clump_to_index(i);
}
};
prev_box = in_boxes[i];
}
// handle remaining clumps
if self.in_clump {
self.flush_clump_to_list(ctx, &temp_boxes);
}
debug!("swapping out boxes.");
// swap out old and new box list of flow
self.flow.inline().boxes.set(dvec::unwrap(temp_boxes));
debug!("new inline flow boxes:");
do self.flow.inline().boxes.each |box| {
debug!("%s", box.debug_str()); true
}
// helper functions
pure fn boxes_can_be_coalesced(a: @RenderBox, b: @RenderBox) -> bool {
assert !core::box::ptr_eq(a, b);
match (a, b) {
// TODO: check whether text styles, fonts are the same.
(@UnscannedTextBox(*), @UnscannedTextBox(*)) => a.can_merge_with_box(b),
(_, _) => false
}
}
}
fn reset_clump_to_index(i: uint) {
debug!("resetting clump to %u", i);
self.clump_start = i;
self.clump_end = i;
self.in_clump = true;
}
fn flush_clump_to_list(ctx: &LayoutContext, temp_boxes: &DVec<@RenderBox>) {
assert self.in_clump;
debug!("flushing when start=%?,end=%?", self.clump_start, self.clump_end);
let in_boxes = self.flow.inline().boxes;
let is_singleton = (self.clump_start == self.clump_end);
let is_text_clump = match in_boxes[self.clump_start] {
@UnscannedTextBox(*) => true,
_ => false
};
// TODO: repair the mapping of DOM elements to boxes if it changed.
// (the mapping does not yet exist; see Issue #103)
match (is_singleton, is_text_clump) {
(false, false) => fail ~"WAT: can't coalesce non-text boxes in flush_clump_to_list()!",
(true, false) => { temp_boxes.push(in_boxes[self.clump_start]); }
(true, true) => {
let text = in_boxes[self.clump_start].raw_text();
// TODO: use actual font for corresponding DOM node to create text run.
let run = TextRun(&*ctx.font_cache.get_test_font(), text);
let box_guts = TextBoxData(@run, 0, text.len());
dummy_boxes.set_elt(i, @TextBox(d, box_guts));
debug!("pushing when start=%?,end=%?", self.clump_start, self.clump_end);
temp_boxes.push(@TextBox(copy *in_boxes[self.clump_start].d(), box_guts));
},
_ => {}
(false, true) => {
let mut run_str : ~str = ~"";
// TODO: is using ropes to construct the merged text any faster?
do uint::range(self.clump_start, self.clump_end+1) |i| {
run_str = str::append(run_str, in_boxes[i].raw_text()); true
}
// TODO: use actual font for corresponding DOM node to create text run.
let run = TextRun(&*ctx.font_cache.get_test_font(), move run_str);
let box_guts = TextBoxData(@run, 0, run.text.len());
debug!("pushing when start=%?,end=%?", self.clump_start, self.clump_end);
temp_boxes.push(@TextBox(copy *in_boxes[self.clump_start].d(), box_guts));
}
}
self.in_clump = false;
}
}
@ -81,25 +191,30 @@ fn InlineFlowData() -> InlineFlowData {
trait InlineLayout {
pure fn starts_inline_flow() -> bool;
fn bubble_widths_inline(ctx: &LayoutContext);
fn assign_widths_inline(ctx: &LayoutContext);
fn assign_height_inline(ctx: &LayoutContext);
fn build_display_list_inline(a: &dl::DisplayListBuilder, b: &Rect<au>, c: &Point2D<au>, d: &dl::DisplayList);
fn bubble_widths_inline(@self, ctx: &LayoutContext);
fn assign_widths_inline(@self, ctx: &LayoutContext);
fn assign_height_inline(@self, ctx: &LayoutContext);
fn build_display_list_inline(@self, a: &dl::DisplayListBuilder, b: &Rect<au>, c: &Point2D<au>, d: &dl::DisplayList);
}
impl FlowContext : InlineLayout {
pure fn starts_inline_flow() -> bool { match self { InlineFlow(*) => true, _ => false } }
fn bubble_widths_inline(ctx: &LayoutContext) {
fn bubble_widths_inline(@self, ctx: &LayoutContext) {
assert self.starts_inline_flow();
// TODO: this is a hack
build_runs_for_flow(ctx, &self.inline().boxes);
debug!("box count: %u", self.inline().boxes.len());
let scanner = TextRunScanner(self);
scanner.scan_for_runs(ctx);
let mut min_width = au(0);
let mut pref_width = au(0);
debug!("/box count: %u", self.inline().boxes.len());
for self.inline().boxes.each |box| {
debug!("measuring box: %s", box.debug_str());
min_width = au::max(min_width, box.get_min_width(ctx));
pref_width = au::max(pref_width, box.get_pref_width(ctx));
}
@ -111,7 +226,7 @@ impl FlowContext : InlineLayout {
/* Recursively (top-down) determines the actual width of child
contexts and boxes. When called on this context, the context has
had its width set by the parent context. */
fn assign_widths_inline(ctx: &LayoutContext) {
fn assign_widths_inline(@self, ctx: &LayoutContext) {
assert self.starts_inline_flow();
/* Perform inline flow with the available width. */
@ -170,12 +285,12 @@ impl FlowContext : InlineLayout {
// 'inline-block' box that created this flow.
}
fn assign_height_inline(_ctx: &LayoutContext) {
fn assign_height_inline(@self, _ctx: &LayoutContext) {
// Don't need to set box or ctx heights, since that is done
// during inline flowing.
}
fn build_display_list_inline(builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
fn build_display_list_inline(@self, builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
offset: &Point2D<au>, list: &dl::DisplayList) {
assert self.starts_inline_flow();

View file

@ -22,11 +22,12 @@ fn RootFlowData() -> RootFlowData {
trait RootLayout {
pure fn starts_root_flow() -> bool;
fn bubble_widths_root(ctx: &LayoutContext);
fn assign_widths_root(ctx: &LayoutContext);
fn assign_height_root(ctx: &LayoutContext);
fn bubble_widths_root(@self, ctx: &LayoutContext);
fn assign_widths_root(@self, ctx: &LayoutContext);
fn assign_height_root(@self, ctx: &LayoutContext);
fn build_display_list_root(a: &dl::DisplayListBuilder, b: &Rect<au>, c: &Point2D<au>, d: &dl::DisplayList);
fn build_display_list_root(@self, a: &dl::DisplayListBuilder, b: &Rect<au>,
c: &Point2D<au>, d: &dl::DisplayList);
}
impl FlowContext : RootLayout {
@ -39,25 +40,25 @@ impl FlowContext : RootLayout {
}
/* defer to the block algorithm */
fn bubble_widths_root(ctx: &LayoutContext) {
fn bubble_widths_root(@self, ctx: &LayoutContext) {
assert self.starts_root_flow();
self.bubble_widths_block(ctx)
}
fn assign_widths_root(ctx: &LayoutContext) {
fn assign_widths_root(@self, ctx: &LayoutContext) {
assert self.starts_root_flow();
self.d().position = copy ctx.screen_size;
self.assign_widths_block(ctx)
}
fn assign_height_root(ctx: &LayoutContext) {
fn assign_height_root(@self, ctx: &LayoutContext) {
assert self.starts_root_flow();
self.assign_height_block(ctx);
}
fn build_display_list_root(builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
fn build_display_list_root(@self, builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
offset: &Point2D<au>, list: &dl::DisplayList) {
assert self.starts_root_flow();

View file

@ -5,7 +5,7 @@ use au::au;
use geom::size::Size2D;
use servo_text::text_run::TextRun;
use servo_text::font_cache::FontCache;
use layout::box::{TextBox, RenderBox};
use layout::box::{TextBox, RenderBox, UnscannedTextBox};
use layout::context::LayoutContext;
pub struct TextBoxData {
@ -22,6 +22,19 @@ pub fn TextBoxData(run: @TextRun, offset: uint, length: uint) -> TextBoxData {
}
}
trait UnscannedMethods {
pure fn raw_text() -> ~str;
}
impl RenderBox : UnscannedMethods {
pure fn raw_text() -> ~str {
match self {
UnscannedTextBox(_, s) => copy s,
_ => fail ~"unsupported operation: box.raw_text() on non-unscanned text box."
}
}
}
/* The main reflow routine for text layout.
impl @RenderBox : TextLayout {
fn reflow_text(ctx: &LayoutContext) {

View file

@ -463,10 +463,10 @@ struct GlyphStore {
// Initializes the glyph store, but doesn't actually shape anything.
// Use the set_glyph, set_glyphs() methods to store glyph data.
fn GlyphStore(text: &str) -> GlyphStore {
assert text.len() > 0;
fn GlyphStore(length: uint) -> GlyphStore {
assert length > 0;
let buffer = vec::from_elem(text.len(), InitialGlyphEntry());
let buffer = vec::from_elem(length, InitialGlyphEntry());
GlyphStore {
entry_buffer: dvec::from_vec(buffer),

View file

@ -26,16 +26,19 @@ impl TextRun {
assert offset < self.text.len();
assert offset + length <= self.text.len();
debug!("enter min_width_for_range(o=%?, l=%?)", offset, length);
let mut max_piece_width = au(0);
// TODO: use a real font reference
let font = ctx.font_cache.get_test_font();
for self.iter_indivisible_pieces_for_range(offset, length) |piece_offset, piece_len| {
let metrics = font.measure_text(&self, piece_offset, piece_len);
if metrics.advance > max_piece_width {
max_piece_width = metrics.advance;
}
};
debug!("exit min_width_for_range(o=%?, l=%?)", offset, length);
return max_piece_width;
}
@ -75,6 +78,8 @@ impl TextRun {
assert offset < self.text.len();
assert offset + length <= self.text.len();
debug!("enter iter_indivisible_pieces_for_range(o=%?, l=%?)", offset, length);
//TODO: need a more sophisticated model of words and possible breaks
let text = str::view(self.text, offset, length);
@ -117,11 +122,12 @@ impl TextRun {
}
}
}
debug!("exit iter_indivisible_pieces_for_range(o=%?, l=%?)", offset, length);
}
}
fn TextRun(font: &Font, +text: ~str) -> TextRun {
let glyph_store = GlyphStore(text);
fn TextRun(font: &Font, text: ~str) -> TextRun {
let glyph_store = GlyphStore(text.len());
let run = TextRun {
text: text,
glyphs: glyph_store,