servo/src/components/main/layout/box_builder.rs
2013-08-20 11:47:50 -07:00

613 lines
26 KiB
Rust

/* 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/. */
//! Creates CSS boxes from a DOM tree.
use layout::block::BlockFlowData;
use layout::float::FloatFlowData;
use layout::box::{GenericRenderBoxClass, ImageRenderBox, ImageRenderBoxClass, RenderBox};
use layout::box::{RenderBoxBase, RenderBoxType, RenderBox_Generic, RenderBox_Image};
use layout::box::{RenderBox_Text, UnscannedTextRenderBox, UnscannedTextRenderBoxClass};
use layout::context::LayoutContext;
use layout::flow::{AbsoluteFlow, BlockFlow, FloatFlow, Flow_Absolute, Flow_Block, Flow_Float};
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};
use newcss::values::{CSSDisplayTable, CSSDisplayInlineTable, CSSDisplayListItem};
use newcss::values::{CSSDisplayTableRowGroup, CSSDisplayTableHeaderGroup, CSSDisplayTableFooterGroup};
use newcss::values::{CSSDisplayTableRow, CSSDisplayTableColumnGroup, CSSDisplayTableColumn};
use newcss::values::{CSSDisplayTableCell, CSSDisplayTableCaption};
use newcss::values::{CSSDisplayNone};
use newcss::values::{CSSFloatNone, CSSFloatLeft, CSSFloatRight};
use layout::float_context::{FloatLeft, FloatRight};
use script::dom::element::*;
use script::dom::node::{AbstractNode, CommentNodeTypeId, DoctypeNodeTypeId};
use script::dom::node::{ElementNodeTypeId, LayoutView, TextNodeTypeId};
use servo_util::range::Range;
use servo_util::tree::{TreeNodeRef, TreeNode};
use std::cell::Cell;
pub struct LayoutTreeBuilder {
next_cid: int,
next_bid: int,
}
impl LayoutTreeBuilder {
pub fn new() -> LayoutTreeBuilder {
LayoutTreeBuilder {
next_cid: -1,
next_bid: -1,
}
}
}
// helper object for building the initial box list and making the
// mapping between DOM nodes and boxes.
struct BoxGenerator<'self> {
flow: &'self mut FlowContext,
range_stack: @mut ~[uint],
}
enum InlineSpacerSide {
LogicalBefore,
LogicalAfter,
}
fn simulate_UA_display_rules(node: AbstractNode<LayoutView>) -> CSSDisplay {
// FIXME
/*let resolved = do node.aux |nd| {
match nd.style.display_type {
Inherit | Initial => DisplayInline, // TODO: remove once resolve works
Specified(v) => v
}
};*/
let resolved = CSSDisplayInline;
if resolved == CSSDisplayNone {
return resolved;
}
match node.type_id() {
DoctypeNodeTypeId | CommentNodeTypeId => CSSDisplayNone,
TextNodeTypeId => CSSDisplayInline,
ElementNodeTypeId(element_type_id) => {
match element_type_id {
HTMLHeadElementTypeId |
HTMLScriptElementTypeId => CSSDisplayNone,
HTMLParagraphElementTypeId |
HTMLDivElementTypeId |
HTMLBodyElementTypeId |
HTMLHeadingElementTypeId |
HTMLHtmlElementTypeId |
HTMLUListElementTypeId |
HTMLOListElementTypeId => CSSDisplayBlock,
_ => resolved
}
}
}
}
impl<'self> BoxGenerator<'self> {
/* Debug ids only */
fn new(flow: &'self mut FlowContext) -> BoxGenerator<'self> {
debug!("Creating box generator for flow: %s", flow.debug_str());
BoxGenerator {
flow: flow,
range_stack: @mut ~[]
}
}
fn with_clone<R> (&mut self, cb: &fn(BoxGenerator<'self>) -> R) -> R {
let gen = BoxGenerator {
flow: &mut *self.flow,
range_stack: self.range_stack
};
cb(gen)
}
/* Whether "spacer" boxes are needed to stand in for this DOM node */
fn inline_spacers_needed_for_node(_: AbstractNode<LayoutView>) -> bool {
return false;
}
// TODO: implement this, generating spacer
fn make_inline_spacer_for_node_side(_: &LayoutContext,
_: AbstractNode<LayoutView>,
_: InlineSpacerSide)
-> Option<RenderBox> {
None
}
pub fn push_node(&mut self,
ctx: &LayoutContext,
node: AbstractNode<LayoutView>,
builder: &mut LayoutTreeBuilder) {
debug!("BoxGenerator[f%d]: pushing node: %s", self.flow.id(), node.debug_str());
// first, determine the box type, based on node characteristics
let simulated_display = simulate_UA_display_rules(node);
// TODO: remove this once UA styles work
let box_type = self.decide_box_type(node, simulated_display);
debug!("BoxGenerator[f%d]: point a", self.flow.id());
let range_stack = &mut self.range_stack;
// depending on flow, make a box for this node.
match *self.flow {
InlineFlow(ref mut inline) => {
let node_range_start = inline.boxes.len();
range_stack.push(node_range_start);
// if a leaf, make a box.
if node.is_leaf() {
let new_box = BoxGenerator::make_box(ctx, box_type, node, builder);
inline.boxes.push(new_box);
} else if BoxGenerator::inline_spacers_needed_for_node(node) {
// else, maybe make a spacer for "left" margin, border, padding
let inline_spacer = BoxGenerator::make_inline_spacer_for_node_side(ctx, node, LogicalBefore);
for spacer in inline_spacer.iter() {
inline.boxes.push(*spacer);
}
}
// TODO: cases for inline-block, etc.
},
BlockFlow(ref mut block) => {
debug!("BoxGenerator[f%d]: point b", block.common.id);
let new_box = BoxGenerator::make_box(ctx, box_type, node, builder);
debug!("BoxGenerator[f%d]: attaching box[b%d] to block flow (node: %s)",
block.common.id,
new_box.id(),
node.debug_str());
assert!(block.box.is_none());
block.box = Some(new_box);
}
FloatFlow(ref mut float) => {
debug!("BoxGenerator[f%d]: point b", float.common.id);
let new_box = BoxGenerator::make_box(ctx, box_type, node, builder);
debug!("BoxGenerator[f%d]: attaching box[b%d] to float flow (node: %s)",
float.common.id,
new_box.id(),
node.debug_str());
assert!(float.box.is_none() && float.index.is_none());
float.box = Some(new_box);
}
_ => warn!("push_node() not implemented for flow f%d", self.flow.id()),
}
}
pub fn pop_node(&mut self,
ctx: &LayoutContext,
node: AbstractNode<LayoutView>) {
debug!("BoxGenerator[f%d]: popping node: %s", self.flow.id(), node.debug_str());
match *self.flow {
InlineFlow(ref mut inline) => {
let inline = &mut *inline;
if BoxGenerator::inline_spacers_needed_for_node(node) {
// If this non-leaf box generates extra horizontal spacing, add a SpacerBox for
// it.
let result = BoxGenerator::make_inline_spacer_for_node_side(ctx, node, LogicalAfter);
for spacer in result.iter() {
let boxes = &mut inline.boxes;
boxes.push(*spacer);
}
}
let mut node_range: Range = Range::new(self.range_stack.pop(), 0);
node_range.extend_to(inline.boxes.len());
if node_range.length() == 0 {
warn!("node range length is zero?!")
}
debug!("BoxGenerator: adding element range=%?", node_range);
inline.elems.add_mapping(node, &node_range);
},
BlockFlow(*) => assert!(self.range_stack.len() == 0),
FloatFlow(*) => assert!(self.range_stack.len() == 0),
_ => warn!("pop_node() not implemented for flow %?", self.flow.id()),
}
}
/// Disambiguate between different methods here instead of inlining, since each case has very
/// different complexity.
fn make_box(layout_ctx: &LayoutContext,
ty: RenderBoxType,
node: AbstractNode<LayoutView>,
builder: &mut LayoutTreeBuilder)
-> RenderBox {
let base = RenderBoxBase::new(node, builder.next_box_id());
let result = match ty {
RenderBox_Generic => GenericRenderBoxClass(@mut base),
RenderBox_Text => UnscannedTextRenderBoxClass(@mut UnscannedTextRenderBox::new(base)),
RenderBox_Image => BoxGenerator::make_image_box(layout_ctx, node, base),
};
debug!("BoxGenerator: created box: %s", result.debug_str());
result
}
fn make_image_box(layout_ctx: &LayoutContext,
node: AbstractNode<LayoutView>,
base: RenderBoxBase)
-> RenderBox {
assert!(node.is_image_element());
do node.with_imm_image_element |image_element| {
if image_element.image.is_some() {
// FIXME(pcwalton): Don't copy URLs.
let url = (*image_element.image.get_ref()).clone();
ImageRenderBoxClass(@mut ImageRenderBox::new(base, url, layout_ctx.image_cache))
} else {
info!("Tried to make image box, but couldn't find image. Made generic box \
instead.");
GenericRenderBoxClass(@mut base)
}
}
}
fn decide_box_type(&self, node: AbstractNode<LayoutView>, _: CSSDisplay) -> RenderBoxType {
if node.is_text() {
RenderBox_Text
} else if node.is_image_element() {
do node.with_imm_image_element |image_element| {
match image_element.image {
Some(_) => RenderBox_Image,
None => RenderBox_Generic,
}
}
} else if node.is_element() {
RenderBox_Generic
} else {
fail!(~"Hey, doctypes and comments shouldn't get here! They are display:none!")
}
}
}
enum BoxGenResult<'self> {
NoGenerator,
ParentGenerator,
SiblingGenerator,
NewGenerator(BoxGenerator<'self>),
Mixed(BoxGenerator<'self>, ~BoxGenResult<'self>),
}
impl LayoutTreeBuilder {
/* Debug-only ids */
pub fn next_flow_id(&mut self) -> int { self.next_cid += 1; self.next_cid }
pub fn next_box_id(&mut self) -> int { self.next_bid += 1; self.next_bid }
/// Creates necessary box(es) and flow context(s) for the current DOM node,
/// and recurses on its children.
pub fn construct_recursively<'a>(&mut self,
layout_ctx: &LayoutContext,
cur_node: AbstractNode<LayoutView>,
mut parent_generator: BoxGenerator<'a>,
mut prev_sibling_generator: Option<BoxGenerator<'a>>)
-> Option<BoxGenerator<'a>> {
debug!("Considering node: %s", cur_node.debug_str());
let box_gen_result = {
let sibling_gen_ref = match prev_sibling_generator {
Some(ref mut generator) => Some(generator),
None => None,
};
self.box_generator_for_node(cur_node, &mut parent_generator, sibling_gen_ref)
};
debug!("result from generator_for_node: %?", &box_gen_result);
// Skip over nodes that don't belong in the flow tree
let (this_generator, next_generator) =
match box_gen_result {
NoGenerator => return prev_sibling_generator,
ParentGenerator => (parent_generator, None),
SiblingGenerator => (prev_sibling_generator.take_unwrap(), None),
NewGenerator(gen) => (gen, None),
Mixed(gen, next_gen) => (gen, Some(match *next_gen {
ParentGenerator => parent_generator,
SiblingGenerator => prev_sibling_generator.take_unwrap(),
_ => fail!("Unexpect BoxGenResult")
}))
};
let mut this_generator = this_generator;
debug!("point a: %s", cur_node.debug_str());
this_generator.push_node(layout_ctx, cur_node, self);
debug!("point b: %s", cur_node.debug_str());
// recurse on child nodes.
let prev_gen_cell = Cell::new(None);
for child_node in cur_node.children() {
do this_generator.with_clone |clone| {
let mut prev_generator = prev_gen_cell.take();
prev_generator = self.construct_recursively(layout_ctx, child_node, clone, prev_generator);
prev_gen_cell.put_back(prev_generator);
}
}
this_generator.pop_node(layout_ctx, cur_node);
self.simplify_children_of_flow(layout_ctx, this_generator.flow);
match next_generator {
Some(n_gen) => Some(n_gen),
None => Some(this_generator),
}
}
pub fn box_generator_for_node<'a>(&mut self,
node: AbstractNode<LayoutView>,
parent_generator: &mut BoxGenerator<'a>,
mut sibling_generator: Option<&mut BoxGenerator<'a>>)
-> BoxGenResult<'a> {
let display = if node.is_element() {
match node.style().display(node.is_root()) {
CSSDisplayNone => return NoGenerator, // tree ends here if 'display: none'
// TODO(eatkinson) these are hacks so that the code doesn't crash
// when unsupported display values are used. They should be deleted
// as they are implemented.
CSSDisplayListItem => CSSDisplayBlock,
CSSDisplayTable => CSSDisplayBlock,
CSSDisplayInlineTable => CSSDisplayInlineBlock,
CSSDisplayTableRowGroup => CSSDisplayBlock,
CSSDisplayTableHeaderGroup => CSSDisplayBlock,
CSSDisplayTableFooterGroup => CSSDisplayBlock,
CSSDisplayTableRow => CSSDisplayBlock,
CSSDisplayTableColumnGroup => return NoGenerator,
CSSDisplayTableColumn => return NoGenerator,
CSSDisplayTableCell => CSSDisplayBlock,
CSSDisplayTableCaption => CSSDisplayBlock,
v => v
}
} else {
match node.type_id() {
ElementNodeTypeId(_) => CSSDisplayInline,
TextNodeTypeId => CSSDisplayInline,
DoctypeNodeTypeId | CommentNodeTypeId => return NoGenerator,
}
};
let sibling_flow: Option<&mut FlowContext> = sibling_generator.map_mut(|gen| &mut *gen.flow);
// TODO(eatkinson): use the value of the float property to
// determine whether to float left or right.
let is_float = if (node.is_element()) {
match node.style().float() {
CSSFloatNone => None,
CSSFloatLeft => Some(FloatLeft),
CSSFloatRight => Some(FloatRight)
}
} else {
None
};
let new_generator = match (display, &mut parent_generator.flow, sibling_flow) {
// Floats
(CSSDisplayBlock, & &BlockFlow(_), _) |
(CSSDisplayBlock, & &FloatFlow(_), _) if !is_float.is_none() => {
self.create_child_generator(node, parent_generator, Flow_Float(is_float.unwrap()))
}
// If we're placing a float after an inline, append the float to the inline flow,
// then continue building from the inline flow in case there are more inlines
// afterward.
(CSSDisplayBlock, _, Some(&InlineFlow(_))) if !is_float.is_none() => {
let float_generator = self.create_child_generator(node,
sibling_generator.unwrap(),
Flow_Float(is_float.unwrap()));
return Mixed(float_generator, ~SiblingGenerator);
}
// This is a catch-all case for when:
// a) sibling_flow is None
// b) sibling_flow is a BlockFlow
(CSSDisplayBlock, & &InlineFlow(_), _) if !is_float.is_none() => {
self.create_child_generator(node, parent_generator, Flow_Float(is_float.unwrap()))
}
(CSSDisplayBlock, & &BlockFlow(ref info), _) => match (info.is_root, node.parent_node().is_some()) {
// If this is the root node, then use the root flow's
// context. Otherwise, make a child block context.
(true, true) => self.create_child_generator(node, parent_generator, Flow_Block),
(true, false) => { return ParentGenerator }
(false, _) => {
self.create_child_generator(node, parent_generator, Flow_Block)
}
},
(CSSDisplayBlock, & &FloatFlow(*), _) => {
self.create_child_generator(node, parent_generator, Flow_Block)
}
// Inlines that are children of inlines are part of the same flow
(CSSDisplayInline, & &InlineFlow(*), _) => return ParentGenerator,
(CSSDisplayInlineBlock, & &InlineFlow(*), _) => return ParentGenerator,
// Inlines that are children of blocks create new flows if their
// previous sibling was a block.
(CSSDisplayInline, & &BlockFlow(*), Some(&BlockFlow(*))) |
(CSSDisplayInlineBlock, & &BlockFlow(*), Some(&BlockFlow(*))) => {
self.create_child_generator(node, parent_generator, Flow_Inline)
}
// The first two cases should only be hit when a FloatFlow
// is the first child of a BlockFlow. Other times, we will
(CSSDisplayInline, _, Some(&FloatFlow(*))) |
(CSSDisplayInlineBlock, _, Some(&FloatFlow(*))) |
(CSSDisplayInline, & &FloatFlow(*), _) |
(CSSDisplayInlineBlock, & &FloatFlow(*), _) => {
self.create_child_generator(node, parent_generator, Flow_Inline)
}
// Inlines whose previous sibling was not a block try to use their
// sibling's flow context.
(CSSDisplayInline, & &BlockFlow(*), _) |
(CSSDisplayInlineBlock, & &BlockFlow(*), _) => {
return match sibling_generator {
None => NewGenerator(self.create_child_generator(node,
parent_generator,
Flow_Inline)),
Some(*) => SiblingGenerator
}
}
// TODO(eatkinson): blocks that are children of inlines need
// to split their parent flows.
_ => return ParentGenerator
};
NewGenerator(new_generator)
}
pub fn create_child_generator<'a>(&mut self,
node: AbstractNode<LayoutView>,
parent_generator: &mut BoxGenerator<'a>,
ty: FlowContextType)
-> BoxGenerator<'a> {
let new_flow = self.make_flow(ty, node);
parent_generator.flow.add_new_child(new_flow);
BoxGenerator::new(parent_generator.flow.last_child().unwrap())
}
/// Fix up any irregularities such as:
///
/// * split inlines (CSS 2.1 Section 9.2.1.1)
/// * elide non-preformatted whitespace-only text boxes and their flows (CSS 2.1 Section
/// 9.2.2.1).
///
/// 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, ctx: &LayoutContext, parent_flow: &mut FlowContext) {
match *parent_flow {
InlineFlow(*) => {
let mut found_child_inline = false;
let mut found_child_block = false;
for child_ctx in parent_flow.child_iter() {
match child_ctx {
&InlineFlow(*) | &InlineBlockFlow(*) => found_child_inline = true,
&BlockFlow(*) => found_child_block = true,
_ => {}
}
}
if found_child_block && found_child_inline {
self.fixup_split_inline(parent_flow)
}
},
BlockFlow(*) | FloatFlow(*) => {
// check first/last child for whitespace-ness
let mut do_remove = false;
let p_id = parent_flow.id();
do parent_flow.with_first_child |mut first_child| {
for first_flow in first_child.mut_iter() {
if first_flow.starts_inline_flow() {
// FIXME: workaround for rust#6393
{
let boxes = &first_flow.imm_inline().boxes;
if boxes.len() == 1 && boxes[0].is_whitespace_only() {
debug!("LayoutTreeBuilder: pruning whitespace-only first child \
flow f%d from parent f%d",
first_flow.id(),
p_id);
do_remove = true;
}
}
}
}
}
if (do_remove) {
parent_flow.remove_first();
}
do_remove = false;
let p_id = parent_flow.id();
do parent_flow.with_last_child |mut last_child| {
for last_flow in last_child.mut_iter() {
if last_flow.starts_inline_flow() {
// FIXME: workaround for rust#6393
{
let boxes = &last_flow.imm_inline().boxes;
if boxes.len() == 1 && boxes.last().is_whitespace_only() {
debug!("LayoutTreeBuilder: pruning whitespace-only last child \
flow f%d from parent f%d",
last_flow.id(),
p_id);
do_remove = true;
}
}
}
}
}
if (do_remove) {
parent_flow.remove_last();
}
// 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 child_flow in (*parent_flow).child_iter() {
match *child_flow {
InlineFlow(*) | InlineBlockFlow(*) => {
let mut scanner = TextRunScanner::new();
scanner.scan_for_runs(ctx, child_flow);
}
_ => {}
}
}
},
_ => {}
}
}
pub fn fixup_split_inline(&self, _: &mut FlowContext) {
// TODO: finish me.
fail!(~"TODO: handle case where an inline is split by a block")
}
/// Entry point for box creation. Should only be called on the root DOM element.
pub fn construct_trees(&mut self, layout_ctx: &LayoutContext, root: AbstractNode<LayoutView>)
-> Result<FlowContext, ()> {
debug!("Constructing flow tree for DOM: ");
root.dump();
let mut new_flow = self.make_flow(Flow_Root, root);
{
let new_generator = BoxGenerator::new(&mut new_flow);
self.construct_recursively(layout_ctx, root, new_generator, None);
}
return Ok(new_flow)
}
/// Creates a flow of the given type for the supplied node.
pub fn make_flow(&mut self, ty: FlowContextType, node: AbstractNode<LayoutView>) -> FlowContext {
let info = FlowData::new(self.next_flow_id(), node);
let result = match ty {
Flow_Absolute => AbsoluteFlow(~info),
Flow_Block => BlockFlow(~BlockFlowData::new(info)),
Flow_Float(f_type) => FloatFlow(~FloatFlowData::new(info, f_type)),
Flow_InlineBlock => InlineBlockFlow(~info),
Flow_Inline => InlineFlow(~InlineFlowData::new(info)),
Flow_Root => BlockFlow(~BlockFlowData::new_root(info)),
Flow_Table => TableFlow(~info),
};
debug!("LayoutTreeBuilder: created flow: %s", result.debug_str());
result
}
}