aboutsummaryrefslogtreecommitdiffstats
path: root/src/servo/layout/box.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/servo/layout/box.rs')
-rw-r--r--src/servo/layout/box.rs949
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)
- }
-}