diff options
Diffstat (limited to 'src/servo/layout/box.rs')
-rw-r--r-- | src/servo/layout/box.rs | 949 |
1 files changed, 556 insertions, 393 deletions
diff --git a/src/servo/layout/box.rs b/src/servo/layout/box.rs index b6b46f7f5d3..a0e8b9f0cad 100644 --- a/src/servo/layout/box.rs +++ b/src/servo/layout/box.rs @@ -2,84 +2,114 @@ * 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/. */ -/* Fundamental layout structures and algorithms. */ +//! The `RenderBox` type, which represents the leaves of the layout tree. use css::node_style::StyledNode; use dom::node::AbstractNode; use layout::context::LayoutContext; -use layout::debug::BoxedMutDebugMethods; -use layout::display_list_builder::DisplayListBuilder; +use layout::debug::DebugMethods; +use layout::display_list_builder::{DisplayListBuilder, ToGfxColor}; use layout::flow::FlowContext; use layout::text::TextBoxData; -use layout; -use newcss::color::{Color, rgb}; -use newcss::complete::CompleteStyle; -use newcss::units::{Cursive, Em, Fantasy, Length, Monospace, Pt, Px, SansSerif, Serif}; -use newcss::values::{CSSBorderWidthLength, CSSBorderWidthMedium}; -use newcss::values::{CSSFontFamilyFamilyName, CSSFontFamilyGenericFamily}; -use newcss::values::{CSSFontSizeLength, CSSFontStyleItalic, CSSFontStyleNormal}; -use newcss::values::{CSSFontStyleOblique, CSSTextAlign}; +use layout::text; -use core::managed; use core::cell::Cell; +use core::managed; use geom::{Point2D, Rect, Size2D}; use gfx::display_list::{DisplayItem, DisplayList}; use gfx::font::{FontStyle, FontWeight300}; use gfx::geometry::Au; use gfx::image::holder::ImageHolder; -use servo_util::range::*; +use gfx::resource::local_image_cache::LocalImageCache; use gfx; +use newcss::color::{Color, rgb}; +use newcss::complete::CompleteStyle; +use newcss::units::{Cursive, Em, Fantasy, Length, Monospace, Pt, Px, SansSerif, Serif}; +use newcss::values::{CSSBorderWidthLength, CSSBorderWidthMedium}; +use newcss::values::{CSSFontFamilyFamilyName, CSSFontFamilyGenericFamily}; +use newcss::values::{CSSFontSizeLength, CSSFontStyleItalic, CSSFontStyleNormal}; +use newcss::values::{CSSFontStyleOblique, CSSTextAlign}; +use servo_util::range::*; use std::arc; +use std::cmp::FuzzyEq; +use std::net::url::Url; + +/// Render boxes (`struct RenderBox`) are the leaves of the layout tree. They cannot position +/// themselves. In general, render boxes do not have a simple correspondence with CSS boxes as in +/// the specification: +/// +/// * Several render boxes may correspond to the same CSS box or DOM node. For example, a CSS text +/// box broken across two lines is represented by two render boxes. +/// +/// * Some CSS boxes are not created at all, such as some anonymous block boxes induced by inline +/// boxes with block-level sibling boxes. In that case, Servo uses an `InlineFlow` with +/// `BlockFlow` siblings; the `InlineFlow` is block-level, but not a block container. It is +/// positioned as if it were a block box, but its children are positioned according to inline +/// flow. +/// +/// A `GenericBox` is an empty box that contributes only borders, margins, padding, and +/// backgrounds. It is analogous to a CSS nonreplaced content box. +/// +/// A box's type influences how its styles are interpreted during layout. For example, replaced +/// content such as images are resized differently from tables, text, or other content. Different +/// types of boxes may also contain custom data; for example, text boxes contain text. +pub enum RenderBox { + GenericRenderBoxClass(@mut RenderBoxBase), + ImageRenderBoxClass(@mut ImageRenderBox), + TextRenderBoxClass(@mut TextRenderBox), + UnscannedTextRenderBoxClass(@mut UnscannedTextRenderBox), +} -/** -Render boxes (`struct RenderBox`) are the leafs of the layout -tree. They cannot position themselves. In general, render boxes do not -have a simple correspondence with CSS boxes as in the specification: - - * Several render boxes may correspond to the same CSS box or DOM - node. For example, a CSS text box broken across two lines is - represented by two render boxes. - - * Some CSS boxes are not created at all, such as some anonymous block - boxes induced by inline boxes with block-level sibling boxes. In - that case, Servo uses an InlineFlow with BlockFlow siblings; the - InlineFlow is block-level, but not a block container. It is - positioned as if it were a block box, but its children are - positioned according to inline flow. - -Fundamental box types include: +/// A box that represents a (replaced content) image and its accompanying borders, shadows, etc. +pub struct ImageRenderBox { + base: RenderBoxBase, + image: ImageHolder, +} - * GenericBox: an empty box that contributes only borders, margins, -padding, backgrounds. It is analogous to a CSS nonreplaced content box. +impl ImageRenderBox { + pub fn new(base: RenderBoxBase, image_url: Url, local_image_cache: @mut LocalImageCache) + -> ImageRenderBox { + assert!(base.node.is_image_element()); - * ImageBox: a box that represents a (replaced content) image and its - accompanying borders, shadows, etc. + ImageRenderBox { + base: base, + image: ImageHolder::new(image_url, local_image_cache), + } + } +} - * TextBox: a box representing a single run of text with a distinct - style. A TextBox may be split into two or more render boxes across - line breaks. Several TextBoxes may correspond to a single DOM text - node. Split text boxes are implemented by referring to subsets of a - master TextRun object. +/// A box representing a single run of text with a distinct style. A `TextRenderBox` may be split +/// into two or more render boxes across line breaks. Several `TextBox`es may correspond to a +/// single DOM text node. Split text boxes are implemented by referring to subsets of a master +/// `TextRun` object. +pub struct TextRenderBox { + base: RenderBoxBase, -*/ + // TODO: Flatten `TextBoxData` into this type. + text_data: TextBoxData, +} -/* A box's kind influences how its styles are interpreted during - layout. For example, replaced content such as images are resized - differently than tables, text, or other content. +/// The data for an unscanned text box. +pub struct UnscannedTextRenderBox { + base: RenderBoxBase, + text: ~str, +} - It also holds data specific to different box types, such as text. -*/ -pub struct RenderBoxData { - /* originating DOM node */ - node: AbstractNode, - /* reference to containing flow context, which this box - participates in */ - ctx: FlowContext, - /* position of this box relative to owning flow */ - position: Rect<Au>, - font_size: Length, - /* TODO (Issue #87): debug only */ - id: int +impl UnscannedTextRenderBox { + /// Creates a new instance of `UnscannedTextRenderBox`. + pub fn new(base: RenderBoxBase) -> UnscannedTextRenderBox { + assert!(base.node.is_text()); + + do base.node.with_imm_text |text_node| { + // FIXME: Don't copy text; atomically reference count it instead. + // FIXME(pcwalton): If we're just looking at node data, do we have to ensure this is + // a text node? + UnscannedTextRenderBox { + base: base, + text: text_node.parent.data.to_str(), + } + } + } } pub enum RenderBoxType { @@ -88,102 +118,186 @@ pub enum RenderBoxType { RenderBox_Text, } -pub enum RenderBox { - GenericBox(RenderBoxData), - ImageBox(RenderBoxData, ImageHolder), - TextBox(RenderBoxData, TextBoxData), - UnscannedTextBox(RenderBoxData, ~str) -} - +/// Represents the outcome of attempting to split a render box. pub enum SplitBoxResult { - CannotSplit(@mut RenderBox), + CannotSplit(RenderBox), // in general, when splitting the left or right side can // be zero length, due to leading/trailing trimmable whitespace - SplitDidFit(Option<@mut RenderBox>, Option<@mut RenderBox>), - SplitDidNotFit(Option<@mut RenderBox>, Option<@mut RenderBox>) + SplitDidFit(Option<RenderBox>, Option<RenderBox>), + SplitDidNotFit(Option<RenderBox>, Option<RenderBox>) +} + +/// Data common to all render boxes. +pub struct RenderBoxBase { + /// The DOM node that this `RenderBox` originates from. + node: AbstractNode, + + /// The reference to the containing flow context which this box participates in. + ctx: FlowContext, + + /// The position of this box relative to its owning flow. + position: Rect<Au>, + + /// The font size. + /// + /// FIXME(pcwalton): Why is this present on non-text-boxes? + font_size: Length, + + /// A debug ID. + /// + /// TODO(#87) Make this only present in debug builds. + id: int } -pub fn RenderBoxData(node: AbstractNode, ctx: FlowContext, id: int) -> RenderBoxData { - RenderBoxData { - node : node, - ctx : ctx, - position : Au::zero_rect(), - font_size: Px(0.0), - id : id +impl RenderBoxBase { + /// Constructs a new `RenderBoxBase` instance. + pub fn new(node: AbstractNode, flow_context: FlowContext, id: int) -> RenderBoxBase { + RenderBoxBase { + node: node, + ctx: flow_context, + position: Au::zero_rect(), + font_size: Px(0.0), + id: id, + } } } -impl<'self> RenderBox { - fn d(&'self mut self) -> &'self mut RenderBoxData { - unsafe { - //Rust #5074 - we can't take mutable references to the - // data that needs to be returned right now. - match self { - &GenericBox(ref d) => cast::transmute(d), - &ImageBox(ref d, _) => cast::transmute(d), - &TextBox(ref d, _) => cast::transmute(d), - &UnscannedTextBox(ref d, _) => cast::transmute(d), +pub impl RenderBox { + /// Borrows this render box immutably in order to work with its common data. + #[inline(always)] + fn with_imm_base<R>(&self, callback: &fn(&RenderBoxBase) -> R) -> R { + match *self { + GenericRenderBoxClass(generic_box) => callback(generic_box), + ImageRenderBoxClass(image_box) => { + let image_box = &*image_box; // FIXME: Borrow check workaround. + callback(&image_box.base) + } + TextRenderBoxClass(text_box) => { + let text_box = &*text_box; // FIXME: Borrow check workaround. + callback(&text_box.base) + } + UnscannedTextRenderBoxClass(unscanned_text_box) => { + let unscanned_text_box = &*unscanned_text_box; // FIXME: Borrow check workaround. + callback(&unscanned_text_box.base) + } + } + } + + /// Borrows this render box mutably in order to work with its common data. + #[inline(always)] + fn with_mut_base<R>(&self, callback: &fn(&mut RenderBoxBase) -> R) -> R { + match *self { + GenericRenderBoxClass(generic_box) => callback(generic_box), + ImageRenderBoxClass(image_box) => { + let image_box = &mut *image_box; // FIXME: Borrow check workaround. + callback(&mut image_box.base) + } + TextRenderBoxClass(text_box) => { + let text_box = &mut *text_box; // FIXME: Borrow check workaround. + callback(&mut text_box.base) + } + UnscannedTextRenderBoxClass(unscanned_text_box) => { + // FIXME: Borrow check workaround. + let unscanned_text_box = &mut *unscanned_text_box; + callback(&mut unscanned_text_box.base) + } + } + } + + /// A convenience function to return the position of this box. + fn position(&self) -> Rect<Au> { + do self.with_imm_base |base| { + base.position + } + } + + /// A convenience function to return the debugging ID of this box. + fn id(&self) -> int { + do self.with_mut_base |base| { + base.id } - } } - fn is_replaced(self) -> bool { - match self { - ImageBox(*) => true, // TODO: form elements, etc + /// Returns true if this element is replaced content. This is true for images, form elements, + /// and so on. + fn is_replaced(&self) -> bool { + match *self { + ImageRenderBoxClass(*) => true, _ => false } } + /// Returns true if this element can be split. This is true for text boxes. fn can_split(&self) -> bool { match *self { - TextBox(*) => true, + TextRenderBoxClass(*) => true, _ => false } } + /// Returns true if this element is an unscanned text box that consists entirely of whitespace. fn is_whitespace_only(&self) -> bool { match *self { - UnscannedTextBox(_, ref raw_text) => raw_text.is_whitespace(), + UnscannedTextRenderBoxClass(unscanned_text_box) => { + let mut unscanned_text_box = &mut *unscanned_text_box; // FIXME: Borrow check. + unscanned_text_box.text.is_whitespace() + } _ => false } } - fn can_merge_with_box(@mut self, other: @mut RenderBox) -> bool { - assert!(!managed::mut_ptr_eq(self, other)); - - match (self, other) { - (@UnscannedTextBox(*), @UnscannedTextBox(*)) => { + /// Determines whether this box can merge with another render box. + fn can_merge_with_box(&self, other: RenderBox) -> bool { + match (self, &other) { + (&UnscannedTextRenderBoxClass(*), &UnscannedTextRenderBoxClass(*)) => { self.font_style() == other.font_style() }, - (@TextBox(_, d1), @TextBox(_, d2)) => managed::ptr_eq(d1.run, d2.run), - (_, _) => false + (&TextRenderBoxClass(text_box_a), &TextRenderBoxClass(text_box_b)) => { + managed::ptr_eq(text_box_a.text_data.run, text_box_b.text_data.run) + } + (_, _) => false, } } - fn split_to_width(@mut self, _ctx: &LayoutContext, max_width: Au, starts_line: bool) -> SplitBoxResult { - match self { - @GenericBox(*) => CannotSplit(self), - @ImageBox(*) => CannotSplit(self), - @UnscannedTextBox(*) => fail!(~"WAT: shouldn't be an unscanned text box here."), - @TextBox(_,data) => { - - let mut pieces_processed_count : uint = 0; - let mut remaining_width : Au = max_width; - let mut left_range = Range::new(data.range.begin(), 0); - let mut right_range : Option<Range> = None; + /// Attempts to split this box so that its width is no more than `max_width`. Fails if this box + /// is an unscanned text box. + fn split_to_width(&self, _: &LayoutContext, max_width: Au, starts_line: bool) + -> SplitBoxResult { + match *self { + GenericRenderBoxClass(*) | ImageRenderBoxClass(*) => CannotSplit(*self), + UnscannedTextRenderBoxClass(*) => { + fail!(~"WAT: shouldn't be an unscanned text box here.") + } + + TextRenderBoxClass(text_box) => { + let text_box = &mut *text_box; // FIXME: Borrow check. + + let mut pieces_processed_count: uint = 0; + let mut remaining_width: Au = max_width; + let mut left_range = Range::new(text_box.text_data.range.begin(), 0); + let mut right_range: Option<Range> = None; + debug!("split_to_width: splitting text box (strlen=%u, range=%?, avail_width=%?)", - data.run.text.len(), data.range, max_width); - do data.run.iter_indivisible_pieces_for_range(&data.range) |piece_range| { + text_box.text_data.run.text.len(), + text_box.text_data.range, + max_width); + + for text_box.text_data.run.iter_indivisible_pieces_for_range( + &text_box.text_data.range) |piece_range| { debug!("split_to_width: considering piece (range=%?, remain_width=%?)", - piece_range, remaining_width); - let metrics = data.run.metrics_for_range(piece_range); + piece_range, + remaining_width); + + let metrics = text_box.text_data.run.metrics_for_range(piece_range); let advance = metrics.advance_width; - let should_continue : bool; + let should_continue: bool; if advance <= remaining_width { should_continue = true; - if starts_line && pieces_processed_count == 0 - && data.run.range_is_trimmable_whitespace(piece_range) { + + if starts_line && + pieces_processed_count == 0 && + text_box.text_data.run.range_is_trimmable_whitespace(piece_range) { debug!("split_to_width: case=skipping leading trimmable whitespace"); left_range.shift_by(piece_range.length() as int); } else { @@ -191,295 +305,369 @@ impl<'self> RenderBox { remaining_width -= advance; left_range.extend_by(piece_range.length() as int); } - } else { /* advance > remaining_width */ + } else { // The advance is more than the remaining width. should_continue = false; - if data.run.range_is_trimmable_whitespace(piece_range) { - // if there are still things after the trimmable whitespace, create right chunk - if piece_range.end() < data.range.end() { - debug!("split_to_width: case=skipping trimmable trailing whitespace, then split remainder"); - right_range = Some(Range::new(piece_range.end(), - data.range.end() - piece_range.end())); + if text_box.text_data.run.range_is_trimmable_whitespace(piece_range) { + // If there are still things after the trimmable whitespace, create the + // right chunk. + if piece_range.end() < text_box.text_data.range.end() { + debug!("split_to_width: case=skipping trimmable trailing \ + whitespace, then split remainder"); + let right_range_end = + text_box.text_data.range.end() - piece_range.end(); + right_range = Some(Range::new(piece_range.end(), right_range_end)); } else { - debug!("split_to_width: case=skipping trimmable trailing whitespace"); + debug!("split_to_width: case=skipping trimmable trailing \ + whitespace"); } - } else if piece_range.begin() < data.range.end() { - // still things left, create right chunk - right_range = Some(Range::new(piece_range.begin(), - data.range.end() - piece_range.begin())); + } else if piece_range.begin() < text_box.text_data.range.end() { + // There are still some things left over at the end of the line. Create + // the right chunk. + let right_range_end = + text_box.text_data.range.end() - piece_range.begin(); + right_range = Some(Range::new(piece_range.begin(), right_range_end)); debug!("split_to_width: case=splitting remainder with right range=%?", right_range); } } + pieces_processed_count += 1; - should_continue + + if !should_continue { + break + } } let left_box = if left_range.length() > 0 { - Some(layout::text::adapt_textbox_with_range(self.d(), data.run, &left_range)) - } else { None }; - - let right_box = right_range.map_default(None, |range: &Range| { - Some(layout::text::adapt_textbox_with_range(self.d(), data.run, range)) - }); + let new_text_box = @mut text::adapt_textbox_with_range(text_box.base, + text_box.text_data.run, + left_range); + Some(TextRenderBoxClass(new_text_box)) + } else { + None + }; + + let right_box = do right_range.map_default(None) |range: &Range| { + let new_text_box = @mut text::adapt_textbox_with_range(text_box.base, + text_box.text_data.run, + *range); + Some(TextRenderBoxClass(new_text_box)) + }; - return if pieces_processed_count == 1 || left_box.is_none() { + if pieces_processed_count == 1 || left_box.is_none() { SplitDidNotFit(left_box, right_box) } else { SplitDidFit(left_box, right_box) } - }, + } } } - /** 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() - */ - fn get_min_width(&mut self, _ctx: &LayoutContext) -> Au { + /// Returns the *minimum width* of this render box as defined by the CSS specification. + fn get_min_width(&self, _: &LayoutContext) -> Au { match *self { - // TODO: this should account for min/pref widths of the - // box element in isolation. That includes - // border/margin/padding but not child widths. The block - // FlowContext will combine the width of this element and - // that of its children to arrive at the context width. - GenericBox(*) => Au(0), - // TODO: consult CSS 'width', margin, border. - // TODO: If image isn't available, consult 'width'. - ImageBox(_, ref mut i) => Au::from_px(i.get_size().get_or_default(Size2D(0,0)).width), - TextBox(_,d) => d.run.min_width_for_range(&d.range), - UnscannedTextBox(*) => fail!(~"Shouldn't see unscanned boxes here.") + // TODO: This should account for the minimum width of the box element in isolation. + // That includes borders, margins, and padding, but not child widths. The block + // `FlowContext` will combine the width of this element and that of its children to + // arrive at the context width. + GenericRenderBoxClass(*) => Au(0), + + ImageRenderBoxClass(image_box) => { + // TODO: Consult the CSS `width` property as well as margins and borders. + // TODO: If the image isn't available, consult `width`. + Au::from_px(image_box.image.get_size().get_or_default(Size2D(0, 0)).width) + } + + TextRenderBoxClass(text_box) => { + let mut text_box = &mut *text_box; // FIXME: Borrow check. + text_box.text_data.run.min_width_for_range(&text_box.text_data.range) + } + + UnscannedTextRenderBoxClass(*) => fail!(~"Shouldn't see unscanned boxes here.") } } - fn get_pref_width(&mut self, _ctx: &LayoutContext) -> Au { - match self { - // TODO: this should account for min/pref widths of the - // box element in isolation. That includes - // border/margin/padding but not child widths. The block - // FlowContext will combine the width of this element and - // that of its children to arrive at the context width. - &GenericBox(*) => Au(0), - &ImageBox(_, ref mut i) => Au::from_px(i.get_size().get_or_default(Size2D(0,0)).width), - - // a text box cannot span lines, so assume that this is an unsplit text box. - - // TODO: If text boxes have been split to wrap lines, then - // they could report a smaller pref width during incremental reflow. - // maybe text boxes should report nothing, and the parent flow could - // factor in min/pref widths of any text runs that it owns. - &TextBox(_,d) => { - let mut max_line_width: Au = Au(0); - for d.run.iter_natural_lines_for_range(&d.range) |line_range| { + /// Returns the *preferred width* of this render box as defined by the CSS specification. + fn get_pref_width(&self, _: &LayoutContext) -> Au { + match *self { + // TODO: This should account for the preferred width of the box element in isolation. + // That includes borders, margins, and padding, but not child widths. The block + // `FlowContext` will combine the width of this element and that of its children to + // arrive at the context width. + GenericRenderBoxClass(*) => Au(0), + + ImageRenderBoxClass(image_box) => { + Au::from_px(image_box.image.get_size().get_or_default(Size2D(0, 0)).width) + } + + TextRenderBoxClass(text_box) => { + let mut text_box = &mut *text_box; // FIXME: Borrow check bug. + + // A text box cannot span lines, so assume that this is an unsplit text box. + // + // TODO: If text boxes have been split to wrap lines, then they could report a + // smaller preferred width during incremental reflow. Maybe text boxes should + // report nothing and the parent flow can factor in minimum/preferred widths of any + // text runs that it owns. + let mut max_line_width = Au(0); + for text_box.text_data.run.iter_natural_lines_for_range(&text_box.text_data.range) + |line_range| { let mut line_width: Au = Au(0); - for d.run.glyphs.iter_glyphs_for_char_range(line_range) |_char_i, glyph| { + for text_box.text_data.run.glyphs.iter_glyphs_for_char_range(line_range) + |_, glyph| { line_width += glyph.advance() } + max_line_width = Au::max(max_line_width, line_width); } max_line_width - }, - &UnscannedTextBox(*) => fail!(~"Shouldn't see unscanned boxes here.") + } + + UnscannedTextRenderBoxClass(*) => fail!(~"Shouldn't see unscanned boxes here."), } } - /* Returns the amount of left, right "fringe" used by this - box. This should be based on margin, border, padding, width. */ + /// Returns the amount of left and right "fringe" used by this box. This is based on margins, + /// borders, padding, and width. fn get_used_width(&self) -> (Au, Au) { - // TODO: this should actually do some computation! - // See CSS 2.1, Section 10.3, 10.4. - + // TODO: This should actually do some computation! See CSS 2.1, Sections 10.3 and 10.4. (Au(0), Au(0)) } - /* Returns the amount of left, right "fringe" used by this - box. This should be based on margin, border, padding, width. */ + /// Returns the amount of left and right "fringe" used by this box. This should be based on + /// margins, borders, padding, and width. fn get_used_height(&self) -> (Au, Au) { - // TODO: this should actually do some computation! - // See CSS 2.1, Section 10.5, 10.6. - + // TODO: This should actually do some computation! See CSS 2.1, Sections 10.5 and 10.6. (Au(0), Au(0)) } - /* The box formed by the content edge, as defined in CSS 2.1 Section 8.1. - Coordinates are relative to the owning flow. */ - fn content_box(&mut self) -> Rect<Au> { - let origin = {copy self.d().position.origin}; - match self { - &ImageBox(_, ref mut i) => { - let size = i.size(); + /// The box formed by the content edge as defined in CSS 2.1 § 8.1. Coordinates are relative to + /// the owning flow. + fn content_box(&self) -> Rect<Au> { + let origin = self.position().origin; + match *self { + ImageRenderBoxClass(image_box) => { Rect { origin: origin, - size: Size2D(Au::from_px(size.width), - Au::from_px(size.height)) + size: image_box.base.position.size, } }, - &GenericBox(*) => { - copy self.d().position - /* FIXME: The following hits an ICE for whatever reason + GenericRenderBoxClass(*) => { + self.position() + + // FIXME: The following hits an ICE for whatever reason. + /* let origin = self.d().position.origin; - let size = self.d().position.size; + let size = self.d().position.size; let (offset_left, offset_right) = self.get_used_width(); let (offset_top, offset_bottom) = self.get_used_height(); Rect { origin: Point2D(origin.x + offset_left, origin.y + offset_top), - size: Size2D(size.width - (offset_left + offset_right), - size.height - (offset_top + offset_bottom)) - }*/ - }, - &TextBox(*) => { - copy self.d().position + size: Size2D(size.width - (offset_left + offset_right), + size.height - (offset_top + offset_bottom)) + } + */ }, - &UnscannedTextBox(*) => fail!(~"Shouldn't see unscanned boxes here.") + TextRenderBoxClass(*) => self.position(), + UnscannedTextRenderBoxClass(*) => fail!(~"Shouldn't see unscanned boxes here.") } } - /* The box formed by the border edge, as defined in CSS 2.1 Section 8.1. - Coordinates are relative to the owning flow. */ - fn border_box(&mut self) -> Rect<Au> { - // TODO: actually compute content_box + padding + border + /// The box formed by the border edge as defined in CSS 2.1 § 8.1. Coordinates are relative to + /// the owning flow. + fn border_box(&self) -> Rect<Au> { + // TODO: Actually compute the content box, padding, and border. self.content_box() } - /* The box fromed by the margin edge, as defined in CSS 2.1 Section 8.1. - Coordinates are relative to the owning flow. */ - fn margin_box(&mut self) -> Rect<Au> { - // TODO: actually compute content_box + padding + border + margin + /// The box formed by the margin edge as defined in CSS 2.1 § 8.1. Coordinates are relative to + /// the owning flow. + fn margin_box(&self) -> Rect<Au> { + // TODO: Actually compute the content_box, padding, border, and margin. self.content_box() } - fn style(&'self mut self) -> CompleteStyle<'self> { - let d: &'self mut RenderBoxData = self.d(); - d.node.style() - } - - fn with_style_of_nearest_element<R>(@mut self, f: &fn(CompleteStyle) -> R) -> R { - let mut node = self.d().node; - while !node.is_element() { - node = node.parent_node().get(); + /// A convenience function to determine whether this render box represents a DOM element. + fn is_element(&self) -> bool { + do self.with_imm_base |base| { + base.node.is_element() } - f(node.style()) } - // TODO: to implement stacking contexts correctly, we need to - // create a set of display lists, one per each layer of a stacking - // context. (CSS 2.1, Section 9.9.1). Each box is passed the list - // set representing the box's stacking context. When asked to - // construct its constituent display items, each box puts its - // DisplayItems into the correct stack layer (according to CSS 2.1 - // Appendix E). and then builder flattens the list at the end. - - /* Methods for building a display list. This is a good candidate - for a function pointer as the number of boxes explodes. - - # Arguments + /// A convenience function to access the computed style of the DOM node that this render box + /// represents. + fn style(&self) -> CompleteStyle { + self.with_imm_base(|base| base.node.style()) + } - * `builder` - the display list builder which manages the coordinate system and options. - * `dirty` - Dirty rectangle, in the coordinate system of the owning flow (self.ctx) - * `origin` - Total offset from display list root flow to this box's owning flow - * `list` - List to which items should be appended - */ - fn build_display_list(@mut self, _builder: &DisplayListBuilder, dirty: &Rect<Au>, - offset: &Point2D<Au>, list: &Cell<DisplayList>) { + /// A convenience function to access the DOM node that this render box represents. + fn node(&self) -> AbstractNode { + self.with_imm_base(|base| base.node) + } - let box_bounds = self.d().position; + /// Returns the nearest ancestor-or-self `Element` to the DOM node that this render box + /// represents. + /// + /// If there is no ancestor-or-self `Element` node, fails. + fn nearest_ancestor_element(&self) -> AbstractNode { + do self.with_imm_base |base| { + let mut node = base.node; + while !node.is_element() { + match node.parent_node() { + None => fail!(~"no nearest element?!"), + Some(parent) => node = parent, + } + } + node + } + } - let abs_box_bounds = box_bounds.translate(offset); + // + // Painting + // + + /// Adds the display items for this render box to the given display list. + /// + /// Arguments: + /// * `builder`: The display list builder, which manages the coordinate system and options. + /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow. + /// * `origin`: The total offset from the display list root flow to the owning flow of this + /// box. + /// * `list`: The display list to which items should be appended. + /// + /// TODO: To implement stacking contexts correctly, we need to create a set of display lists, + /// one per layer of the stacking context (CSS 2.1 § 9.9.1). Each box is passed the list set + /// representing the box's stacking context. When asked to construct its constituent display + /// items, each box puts its display items into the correct stack layer according to CSS 2.1 + /// Appendix E. Finally, the builder flattens the list. + fn build_display_list(&self, + _: &DisplayListBuilder, + dirty: &Rect<Au>, + offset: &Point2D<Au>, + list: &Cell<DisplayList>) { + let box_bounds = self.position(); + let absolute_box_bounds = box_bounds.translate(offset); debug!("RenderBox::build_display_list at rel=%?, abs=%?: %s", - box_bounds, abs_box_bounds, self.debug_str()); + box_bounds, absolute_box_bounds, self.debug_str()); debug!("RenderBox::build_display_list: dirty=%?, offset=%?", dirty, offset); - if abs_box_bounds.intersects(dirty) { + + if absolute_box_bounds.intersects(dirty) { debug!("RenderBox::build_display_list: intersected. Adding display item..."); } else { debug!("RenderBox::build_display_list: Did not intersect..."); return; } - self.add_bgcolor_to_list(list, &abs_box_bounds); + // Add the background to the list, if applicable. + self.paint_background_if_applicable(list, &absolute_box_bounds); + + match *self { + UnscannedTextRenderBoxClass(*) => fail!(~"Shouldn't see unscanned boxes here."), + TextRenderBoxClass(text_box) => { + let text_box = &mut *text_box; // FIXME: Borrow check bug. - let m = &mut *self; - match m { - &UnscannedTextBox(*) => fail!(~"Shouldn't see unscanned boxes here."), - &TextBox(_,data) => { let nearest_ancestor_element = self.nearest_ancestor_element(); let color = nearest_ancestor_element.style().color().to_gfx_color(); - let mut l = list.take(); // FIXME: this should use with_mut_ref when that appears - l.append_item(~DisplayItem::new_Text(&abs_box_bounds, - ~data.run.serialize(), - data.range, - color)); - list.put_back(l); - - // debug frames for text box bounds + + // FIXME: This should use `with_mut_ref` when that appears. + let mut this_list = list.take(); + this_list.append_item(~DisplayItem::new_Text(&absolute_box_bounds, + ~text_box.text_data.run.serialize(), + text_box.text_data.range, + color)); + list.put_back(this_list); + + // Draw debug frames for text bounds. + // + // FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We + // should have a real `SERVO_DEBUG` system. debug!("%?", { - // text box bounds - let mut l = list.take(); // FIXME: use with_mut_ref when that appears - l.append_item(~DisplayItem::new_Border(&abs_box_bounds, - Au::from_px(1), - rgb(0, 0, 200).to_gfx_color())); - // baseline "rect" - // TODO(Issue #221): create and use a Line display item for baseline. - let ascent = data.run.metrics_for_range(&data.range).ascent; - let baseline = Rect(abs_box_bounds.origin + Point2D(Au(0),ascent), - Size2D(abs_box_bounds.size.width, Au(0))); - - l.append_item(~DisplayItem::new_Border(&baseline, - Au::from_px(1), - rgb(0, 200, 0).to_gfx_color())); - list.put_back(l); - ; ()}); + // Compute the text box bounds. + // + // FIXME: This should use `with_mut_ref` when that appears. + let mut this_list = list.take(); + this_list.append_item(~DisplayItem::new_Border(&absolute_box_bounds, + Au::from_px(1), + rgb(0, 0, 200).to_gfx_color())); + + // Draw a rectangle representing the baselines. + // + // TODO(Issue #221): Create and use a Line display item for the baseline. + let ascent = text_box.text_data.run.metrics_for_range( + &text_box.text_data.range).ascent; + let baseline = Rect(absolute_box_bounds.origin + Point2D(Au(0), ascent), + Size2D(absolute_box_bounds.size.width, Au(0))); + + this_list.append_item(~DisplayItem::new_Border(&baseline, + Au::from_px(1), + rgb(0, 200, 0).to_gfx_color())); + list.put_back(this_list); + () + }); }, - // TODO: items for background, border, outline - &GenericBox(_) => {} - &ImageBox(_, ref mut i) => { - //let i: &mut ImageHolder = unsafe { cast::transmute(i) }; // Rust #5074 - match i.get_image() { + + GenericRenderBoxClass(_) => {} + + ImageRenderBoxClass(image_box) => { + match image_box.image.get_image() { Some(image) => { debug!("(building display list) building image box"); - let mut l = list.take(); // FIXME: use with_mut_ref when available - l.append_item(~DisplayItem::new_Image(&abs_box_bounds, - arc::clone(&image))); - list.put_back(l); + + // FIXME: This should use `with_mut_ref` when that appears. + let mut this_list = list.take(); + this_list.append_item(~DisplayItem::new_Image(&absolute_box_bounds, + arc::clone(&image))); + list.put_back(this_list); } None => { - /* No image data at all? Okay, add some fallback content instead. */ + // No image data at all? Do nothing. + // + // TODO: Add some kind of placeholder image. debug!("(building display list) no image :("); } } } } - self.add_border_to_list(list, &abs_box_bounds); + // Add a border, if applicable. + // + // TODO: Outlines. + self.paint_borders_if_applicable(list, &absolute_box_bounds); } - fn add_bgcolor_to_list(&mut self, list: &Cell<DisplayList>, abs_bounds: &Rect<Au>) { - use std::cmp::FuzzyEq; - + /// Adds the display items necessary to paint the background of this render box to the display + /// list if necessary. + fn paint_background_if_applicable(&self, + list: &Cell<DisplayList>, + absolute_bounds: &Rect<Au>) { // FIXME: This causes a lot of background colors to be displayed when they are clearly not // needed. We could use display list optimization to clean this up, but it still seems // inefficient. What we really want is something like "nearest ancestor element that - // doesn't have a RenderBox". + // doesn't have a render box". let nearest_ancestor_element = self.nearest_ancestor_element(); let bgcolor = nearest_ancestor_element.style().background_color(); if !bgcolor.alpha.fuzzy_eq(&0.0) { let mut l = list.take(); // FIXME: use with_mut_ref when available - l.append_item(~DisplayItem::new_SolidColor(abs_bounds, bgcolor.to_gfx_color())); + l.append_item(~DisplayItem::new_SolidColor(absolute_bounds, bgcolor.to_gfx_color())); list.put_back(l); } } - fn add_border_to_list(&mut self, list: &Cell<DisplayList>, abs_bounds: &Rect<Au>) { - if !self.d().node.is_element() { return } - - let top_width = self.style().border_top_width(); - let right_width = self.style().border_right_width(); - let bottom_width = self.style().border_bottom_width(); - let left_width = self.style().border_left_width(); + /// Adds the display items necessary to paint the borders of this render box to the display + /// list if necessary. + fn paint_borders_if_applicable(&self, list: &Cell<DisplayList>, abs_bounds: &Rect<Au>) { + if !self.is_element() { + return + } + let style = self.style(); + let (top_width, right_width) = (style.border_top_width(), style.border_right_width()); + let (bottom_width, left_width) = (style.border_bottom_width(), style.border_left_width()); match (top_width, right_width, bottom_width, left_width) { (CSSBorderWidthLength(Px(top)), CSSBorderWidthLength(Px(right)), @@ -490,9 +678,8 @@ impl<'self> RenderBox { let bottom_au = Au::from_frac_px(bottom); let left_au = Au::from_frac_px(left); - let all_widths_equal = [top_au, right_au, bottom_au].all(|a| *a == left_au); - - if all_widths_equal { + // Are all the widths equal? + if [ top_au, right_au, bottom_au ].all(|a| *a == left_au) { let border_width = top_au; let bounds = Rect { origin: Point2D { @@ -507,9 +694,11 @@ impl<'self> RenderBox { let top_color = self.style().border_top_color(); let color = top_color.to_gfx_color(); // FIXME - let mut l = list.take(); // FIXME: use with_mut_ref when available - l.append_item(~DisplayItem::new_Border(&bounds, border_width, color)); - list.put_back(l); + + // FIXME: Use `with_mut_ref` when that works. + let mut this_list = list.take(); + this_list.append_item(~DisplayItem::new_Border(&bounds, border_width, color)); + list.put_back(this_list); } else { warn!("ignoring unimplemented border widths"); } @@ -518,121 +707,95 @@ impl<'self> RenderBox { CSSBorderWidthMedium, CSSBorderWidthMedium, CSSBorderWidthMedium) => { - // FIXME: This seems to be the default for non-root nodes. For now we'll ignore it + // FIXME: This seems to be the default for non-root nodes. For now we'll ignore it. warn!("ignoring medium border widths"); } _ => warn!("ignoring unimplemented border widths") } } - // Converts this node's ComputedStyle to a font style used in the graphics code. - fn font_style(@mut self) -> FontStyle { - do self.with_style_of_nearest_element |my_style| { - let font_families = do my_style.font_family().map |family| { - match *family { - CSSFontFamilyFamilyName(ref family_str) => copy *family_str, - CSSFontFamilyGenericFamily(Serif) => ~"serif", - CSSFontFamilyGenericFamily(SansSerif) => ~"sans-serif", - CSSFontFamilyGenericFamily(Cursive) => ~"cursive", - CSSFontFamilyGenericFamily(Fantasy) => ~"fantasy", - CSSFontFamilyGenericFamily(Monospace) => ~"monospace", - } - }; - let font_families = str::connect(font_families, ~", "); - debug!("(font style) font families: `%s`", font_families); - - let font_size = match my_style.font_size() { - CSSFontSizeLength(Px(l)) | - CSSFontSizeLength(Pt(l)) => l, - CSSFontSizeLength(Em(l)) => l, - _ => 16f - }; - debug!("(font style) font size: `%f`", font_size); - - let italic, oblique; - match my_style.font_style() { - CSSFontStyleNormal => { italic = false; oblique = false; } - CSSFontStyleItalic => { italic = true; oblique = false; } - CSSFontStyleOblique => { italic = false; oblique = true; } + /// Converts this node's computed style to a font style used for rendering. + fn font_style(&self) -> FontStyle { + let my_style = self.nearest_ancestor_element().style(); + + // FIXME: Too much allocation here. + let font_families = do my_style.font_family().map |family| { + match *family { + CSSFontFamilyFamilyName(ref family_str) => copy *family_str, + CSSFontFamilyGenericFamily(Serif) => ~"serif", + CSSFontFamilyGenericFamily(SansSerif) => ~"sans-serif", + CSSFontFamilyGenericFamily(Cursive) => ~"cursive", + CSSFontFamilyGenericFamily(Fantasy) => ~"fantasy", + CSSFontFamilyGenericFamily(Monospace) => ~"monospace", } + }; + let font_families = str::connect(font_families, ~", "); + debug!("(font style) font families: `%s`", font_families); + + let font_size = match my_style.font_size() { + CSSFontSizeLength(Px(length)) | + CSSFontSizeLength(Pt(length)) | + CSSFontSizeLength(Em(length)) => length, + _ => 16.0 + }; + debug!("(font style) font size: `%f`", font_size); - FontStyle { - pt_size: font_size, - weight: FontWeight300, - italic: italic, - oblique: oblique, - families: font_families, - } + let (italic, oblique) = match my_style.font_style() { + CSSFontStyleNormal => (false, false), + CSSFontStyleItalic => (true, false), + CSSFontStyleOblique => (false, true), + }; + + FontStyle { + pt_size: font_size, + weight: FontWeight300, + italic: italic, + oblique: oblique, + families: font_families, } } - // Converts this node's ComputedStyle to a text alignment used in the inline layout code. - fn text_align(@mut self) -> CSSTextAlign { - do self.with_style_of_nearest_element |my_style| { - my_style.text_align() - } + /// Returns the text alignment of the computed style of the nearest ancestor-or-self `Element` + /// node. + fn text_align(&self) -> CSSTextAlign { + self.nearest_ancestor_element().style().text_align() } } -impl BoxedMutDebugMethods for RenderBox { - fn dump(@mut self) { - self.dump_indent(0u); +impl DebugMethods for RenderBox { + fn dump(&self) { + self.dump_indent(0); } - /* Dumps the node tree, for debugging, with indentation. */ - fn dump_indent(@mut self, indent: uint) { - let mut s = ~""; + /// Dumps a render box for debugging, with indentation. + fn dump_indent(&self, indent: uint) { + let mut string = ~""; for uint::range(0u, indent) |_i| { - s += ~" "; + string += ~" "; } - s += self.debug_str(); - debug!("%s", s); + string += self.debug_str(); + debug!("%s", string); } - fn debug_str(@mut self) -> ~str { - let borrowed_self : &mut RenderBox = self; // FIXME: borrow checker workaround - let repr = match borrowed_self { - &GenericBox(*) => ~"GenericBox", - &ImageBox(*) => ~"ImageBox", - &TextBox(_,d) => fmt!("TextBox(text=%s)", str::substr(d.run.text, d.range.begin(), d.range.length())), - &UnscannedTextBox(_, ref s) => { - let s = s; // FIXME: borrow checker workaround - fmt!("UnscannedTextBox(%s)", *s) + /// Returns a debugging string describing this box. + fn debug_str(&self) -> ~str { + let representation = match *self { + GenericRenderBoxClass(*) => ~"GenericRenderBox", + ImageRenderBoxClass(*) => ~"ImageRenderBox", + TextRenderBoxClass(text_box) => { + let mut text_box = &mut *text_box; // FIXME: Borrow check bug. + fmt!("TextRenderBox(text=%s)", str::substr(text_box.text_data.run.text, + text_box.text_data.range.begin(), + text_box.text_data.range.length())) + } + UnscannedTextRenderBoxClass(text_box) => { + let mut text_box = &mut *text_box; // FIXME: Borrow check bug. + fmt!("UnscannedTextRenderBox(%s)", text_box.text) } }; - let borrowed_self : &mut RenderBox = self; // FIXME: borrow checker workaround - let id = borrowed_self.d().id; - fmt!("box b%?: %?", id, repr) + fmt!("box b%?: %s", self.id(), representation) } } -// Other methods -impl RenderBox { - /// Returns the nearest ancestor-or-self element node. Infallible. - fn nearest_ancestor_element(&mut self) -> AbstractNode { - let mut node = self.d().node; - while !node.is_element() { - match node.parent_node() { - None => fail!(~"no nearest element?!"), - Some(parent) => node = parent, - } - } - node - } -} - -// FIXME: This belongs somewhere else -trait ToGfxColor { - fn to_gfx_color(&self) -> gfx::color::Color; -} - -impl ToGfxColor for Color { - fn to_gfx_color(&self) -> gfx::color::Color { - gfx::color::rgba(self.red, - self.green, - self.blue, - self.alpha) - } -} |