From 4fda26f76ee6d3a1319827653c5dcd899b974795 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 2 Dec 2013 21:45:25 -0800 Subject: [PATCH] layout: Change `RenderBox` to an enum and shorten its name in preparation for removing its `@`-ness. Also removes a few text copies that were taking place. This sure does remove a lot of code! --- src/components/main/layout/block.rs | 160 ++- src/components/main/layout/box.rs | 1081 +++++++---------- src/components/main/layout/construct.rs | 83 +- .../main/layout/display_list_builder.rs | 16 +- src/components/main/layout/flow.rs | 24 +- src/components/main/layout/inline.rs | 181 ++- src/components/main/layout/text.rs | 84 +- src/components/main/layout/util.rs | 7 +- 8 files changed, 679 insertions(+), 957 deletions(-) diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index b1f94c258ab..d6b54596e4d 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -2,9 +2,9 @@ * 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/. */ -//! CSS block layout. +//! CSS block formatting contexts. -use layout::box::{RenderBox, RenderBoxUtils}; +use layout::box::Box; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::flow::{BlockFlowClass, FlowClass, Flow, FlowData, ImmutableFlowUtils}; @@ -13,13 +13,12 @@ use layout::model::{MaybeAuto, Specified, Auto, specified_or_none, specified}; use layout::float_context::{FloatContext, PlacementInfo, Invalid, FloatType}; use std::cell::Cell; -use geom::point::Point2D; -use geom::size::Size2D; -use geom::rect::Rect; +use geom::{Point2D, Rect, SideOffsets2D, Size2D}; use gfx::display_list::DisplayList; use servo_util::geometry::{Au, to_frac_px}; use servo_util::geometry; +/// Information specific to floated blocks. pub struct FloatedBlockInfo { containing_width: Au, @@ -48,12 +47,13 @@ impl FloatedBlockInfo { } } +/// A block formatting context. pub struct BlockFlow { /// Data common to all flows. base: FlowData, - /// The associated render box. - box: Option<@RenderBox>, + /// The associated box. + box: Option<@Box>, /// Whether this block flow is the root flow. is_root: bool, @@ -72,7 +72,7 @@ impl BlockFlow { } } - pub fn from_box(base: FlowData, box: @RenderBox) -> BlockFlow { + pub fn from_box(base: FlowData, box: @Box) -> BlockFlow { BlockFlow { base: base, box: Some(box), @@ -81,7 +81,7 @@ impl BlockFlow { } } - pub fn float_from_box(base: FlowData, float_type: FloatType, box: @RenderBox) -> BlockFlow { + pub fn float_from_box(base: FlowData, float_type: FloatType, box: @Box) -> BlockFlow { BlockFlow { base: base, box: Some(box), @@ -172,9 +172,9 @@ impl BlockFlow { (width_Au, left_margin_Au, right_margin_Au) } - fn compute_block_margins(&self, box: @RenderBox, remaining_width: Au, available_width: Au) -> (Au, Au, Au) { - let base = box.base(); - let style = base.style(); + fn compute_block_margins(&self, box: @Box, remaining_width: Au, available_width: Au) + -> (Au, Au, Au) { + let style = box.style(); let (width, maybe_margin_left, maybe_margin_right) = (MaybeAuto::from_style(style.Box.width, remaining_width), @@ -215,8 +215,8 @@ impl BlockFlow { return (width, margin_left, margin_right); } - fn compute_float_margins(&self, box: @RenderBox, remaining_width: Au) -> (Au, Au, Au) { - let style = box.base().style(); + fn compute_float_margins(&self, box: @Box, remaining_width: Au) -> (Au, Au, Au) { + let style = box.style(); let margin_left = MaybeAuto::from_style(style.Margin.margin_left, remaining_width).specified_or_zero(); let margin_right = MaybeAuto::from_style(style.Margin.margin_right, @@ -241,18 +241,19 @@ impl BlockFlow { let mut float_ctx = Invalid; for &box in self.box.iter() { - let base = box.base(); - clearance = match base.clear() { + clearance = match box.clear() { None => Au::new(0), Some(clear) => { self.base.floats_in.clearance(clear) } }; - top_offset = clearance + base.margin.top + base.border.top + base.padding.top; + top_offset = clearance + box.margin.get().top + box.border.get().top + + box.padding.get().top; cur_y = cur_y + top_offset; - bottom_offset = base.margin.bottom + base.border.bottom + base.padding.bottom; - left_offset = base.offset(); + bottom_offset = box.margin.get().bottom + box.border.get().bottom + + box.padding.get().bottom; + left_offset = box.offset(); } if inorder { @@ -279,16 +280,16 @@ impl BlockFlow { let mut bottom_margin_collapsible = false; let mut first_in_flow = true; for &box in self.box.iter() { - let base = box.base(); - if !self.is_root && base.border.top == Au::new(0) && base.padding.top == Au::new(0) { - collapsible = base.margin.top; + if !self.is_root && box.border.get().top == Au(0) && box.padding.get().top == Au(0) { + collapsible = box.margin.get().top; top_margin_collapsible = true; } - if !self.is_root && base.border.bottom == Au::new(0) && base.padding.bottom == Au::new(0) { + if !self.is_root && box.border.get().bottom == Au(0) && + box.padding.get().bottom == Au(0) { bottom_margin_collapsible = true; } - margin_top = base.margin.top; - margin_bottom = base.margin.bottom; + margin_top = box.margin.get().top; + margin_bottom = box.margin.get().bottom; } for kid in self.base.child_iter() { @@ -328,8 +329,7 @@ impl BlockFlow { }; for &box in self.box.iter() { - let base = box.base(); - let style = base.style(); + let style = box.style(); height = match MaybeAuto::from_style(style.Box.height, Au::new(0)) { Auto => height, Specified(value) => value @@ -338,22 +338,23 @@ impl BlockFlow { let mut noncontent_height = Au::new(0); for box in self.box.iter() { - let base = box.mut_base(); - let mut position_ref = base.position.mutate(); - let position = &mut position_ref.ptr; + let mut position = box.position.get(); + let mut margin = box.margin.get(); // The associated box is the border box of this flow. - base.margin.top = margin_top; - base.margin.bottom = margin_bottom; + margin.top = margin_top; + margin.bottom = margin_bottom; - position.origin.y = clearance + base.margin.top; + position.origin.y = clearance + margin.top; - noncontent_height = base.padding.top + base.padding.bottom + base.border.top + - base.border.bottom; + noncontent_height = box.padding.get().top + box.padding.get().bottom + + box.border.get().top + box.border.get().bottom; position.size.height = height + noncontent_height; - noncontent_height = noncontent_height + clearance + base.margin.top + - base.margin.bottom; + noncontent_height = noncontent_height + clearance + margin.top + margin.bottom; + + box.position.set(position); + box.margin.set(margin); } self.base.position.size.height = height + noncontent_height; @@ -376,18 +377,18 @@ impl BlockFlow { let mut margin_height = Au(0); for box in self.box.iter() { - let base = box.base(); - height = base.position.borrow().ptr.size.height; - clearance = match base.clear() { + height = box.position.get().size.height; + clearance = match box.clear() { None => Au(0), Some(clear) => self.base.floats_in.clearance(clear), }; - let noncontent_width = base.padding.left + base.padding.right + base.border.left + - base.border.right; + let noncontent_width = box.padding.get().left + box.padding.get().right + + box.border.get().left + box.border.get().right; - full_noncontent_width = noncontent_width + base.margin.left + base.margin.right; - margin_height = base.margin.top + base.margin.bottom; + full_noncontent_width = noncontent_width + box.margin.get().left + + box.margin.get().right; + margin_height = box.margin.get().top + box.margin.get().bottom; } let info = PlacementInfo { @@ -419,8 +420,7 @@ impl BlockFlow { let mut top_offset = Au(0); for &box in self.box.iter() { - let base = box.base(); - top_offset = base.margin.top + base.border.top + base.padding.top; + top_offset = box.margin.get().top + box.border.get().top + box.padding.get().top; cur_y = cur_y + top_offset; } @@ -434,24 +434,23 @@ impl BlockFlow { let mut noncontent_height; let box = self.box.as_ref().unwrap(); - let base = box.base(); - let mut position_ref = base.position.mutate(); - let position = &mut position_ref.ptr; + let mut position = box.position.get(); // The associated box is the border box of this flow. - position.origin.y = base.margin.top; + position.origin.y = box.margin.get().top; - noncontent_height = base.padding.top + base.padding.bottom + base.border.top + - base.border.bottom; + noncontent_height = box.padding.get().top + box.padding.get().bottom + + box.border.get().top + box.border.get().bottom; //TODO(eatkinson): compute heights properly using the 'height' property. - let height_prop = MaybeAuto::from_style(base.style().Box.height, + let height_prop = MaybeAuto::from_style(box.style().Box.height, Au::new(0)).specified_or_zero(); height = geometry::max(height, height_prop) + noncontent_height; debug!("assign_height_float -- height: {}", height); position.size.height = height; + box.position.set(position); } pub fn build_display_list_block( @@ -466,18 +465,16 @@ impl BlockFlow { if self.base.node.is_iframe_element() { let x = self.base.abs_position.x + do self.box.map_default(Au::new(0)) |box| { - let base = box.base(); - base.margin.left + base.border.left + base.padding.left + box.margin.get().left + box.border.get().left + box.padding.get().left }; let y = self.base.abs_position.y + do self.box.map_default(Au::new(0)) |box| { - let base = box.base(); - base.margin.top + base.border.top + base.padding.top + box.margin.get().top + box.border.get().top + box.padding.get().top }; let w = self.base.position.size.width - do self.box.map_default(Au::new(0)) |box| { - box.base().noncontent_width() + box.noncontent_width() }; let h = self.base.position.size.height - do self.box.map_default(Au::new(0)) |box| { - box.base().noncontent_height() + box.noncontent_height() }; do self.base.node.with_mut_iframe_element |iframe_element| { iframe_element.size.get_mut_ref().set_rect(Rect(Point2D(to_frac_px(x) as f32, @@ -587,7 +584,7 @@ impl Flow for BlockFlow { for box in self.box.iter() { { // Can compute border width here since it doesn't depend on anything. - box.mut_base().compute_borders(box.base().style()); + box.compute_borders(box.style()) } let (this_minimum_width, this_preferred_width) = box.minimum_and_preferred_widths(); @@ -631,16 +628,13 @@ impl Flow for BlockFlow { } for &box in self.box.iter() { - let base = box.base(); - let mut_base = box.mut_base(); - let style = base.style(); - let mut_position = &mut base.position.mutate().ptr; + let style = box.style(); // Can compute padding here since we know containing block width. - mut_base.compute_padding(style, remaining_width); + box.compute_padding(style, remaining_width); // Margins are 0 right now so base.noncontent_width() is just borders + padding. - let available_width = remaining_width - base.noncontent_width(); + let available_width = remaining_width - box.noncontent_width(); // Top and bottom margins for blocks are 0 if auto. let margin_top = MaybeAuto::from_style(style.Margin.margin_top, @@ -654,20 +648,20 @@ impl Flow for BlockFlow { self.compute_block_margins(box, remaining_width, available_width) }; - mut_base.margin.top = margin_top; - mut_base.margin.right = margin_right; - mut_base.margin.bottom = margin_bottom; - mut_base.margin.left = margin_left; + box.margin.set(SideOffsets2D::new(margin_top, + margin_right, + margin_bottom, + margin_left)); - x_offset = base.offset(); + x_offset = box.offset(); remaining_width = width; - //The associated box is the border box of this flow - mut_position.origin.x = base.margin.left; - - let padding_and_borders = base.padding.left + base.padding.right + - base.border.left + base.border.right; - mut_position.size.width = remaining_width + padding_and_borders; + // The associated box is the border box of this flow. + let position_ref = box.position.mutate(); + position_ref.ptr.origin.x = box.margin.get().left; + let padding_and_borders = box.padding.get().left + box.padding.get().right + + box.border.get().left + box.border.get().right; + position_ref.ptr.size.width = remaining_width + padding_and_borders; } if self.is_float() { @@ -734,25 +728,23 @@ impl Flow for BlockFlow { } for &box in self.box.iter() { - let base = box.base(); - // The top margin collapses with its first in-flow block-level child's // top margin if the parent has no top border, no top padding. if *first_in_flow && top_margin_collapsible { // If top-margin of parent is less than top-margin of its first child, // the parent box goes down until its top is aligned with the child. - if *margin_top < base.margin.top { + if *margin_top < box.margin.get().top { // TODO: The position of child floats should be updated and this // would influence clearance as well. See #725 - let extra_margin = base.margin.top - *margin_top; + let extra_margin = box.margin.get().top - *margin_top; *top_offset = *top_offset + extra_margin; - *margin_top = base.margin.top; + *margin_top = box.margin.get().top; } } // The bottom margin of an in-flow block-level element collapses // with the top margin of its next in-flow block-level sibling. - *collapsing = geometry::min(base.margin.top, *collapsible); - *collapsible = base.margin.bottom; + *collapsing = geometry::min(box.margin.get().top, *collapsible); + *collapsible = box.margin.get().bottom; } *first_in_flow = false; diff --git a/src/components/main/layout/box.rs b/src/components/main/layout/box.rs index d2c046fbd9e..904b5795b83 100644 --- a/src/components/main/layout/box.rs +++ b/src/components/main/layout/box.rs @@ -2,17 +2,17 @@ * 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/. */ -//! The `RenderBox` type, which represents the leaves of the layout tree. +//! The `Box` type, which represents the leaves of the layout tree. use extra::url::Url; use geom::{Point2D, Rect, Size2D, SideOffsets2D}; +use gfx::color::rgb; use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass}; use gfx::display_list::{DisplayList, ImageDisplayItem, ImageDisplayItemClass}; use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, TextDisplayItem}; use gfx::display_list::{TextDisplayItemClass, ClipDisplayItem, ClipDisplayItemClass}; use gfx::font::{FontStyle, FontWeight300}; use gfx::text::text_run::TextRun; -use gfx::color::rgb; use script::dom::node::{AbstractNode, LayoutView}; use servo_net::image::holder::ImageHolder; use servo_net::local_image_cache::LocalImageCache; @@ -24,12 +24,10 @@ use std::cast; use std::cell::Cell; use std::cmp::ApproxEq; use std::num::Zero; -use std::unstable::raw::Box; use style::ComputedValues; -use style::computed_values::{ - border_style, clear, float, font_family, font_style, line_height, - position, text_align, text_decoration, vertical_align, LengthOrPercentage, - overflow, visibility}; +use style::computed_values::{LengthOrPercentage, overflow}; +use style::computed_values::{border_style, clear, float, font_family, font_style, line_height}; +use style::computed_values::{position, text_align, text_decoration, vertical_align, visibility}; use css::node_style::StyledNode; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor}; @@ -54,211 +52,62 @@ use layout::model::{MaybeAuto, specified}; /// 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 trait RenderBox { - /// Returns the class of render box that this is. - fn class(&self) -> RenderBoxClass; +/// +/// FIXME(pcwalton): This can be slimmed down quite a bit. +pub struct Box { + /// The DOM node that this `Box` originates from. + node: AbstractNode, - /// If this is an image render box, returns the underlying object. Fails otherwise. + /// The position of this box relative to its owning flow. + position: Slot>, + + /// The border of the content box. /// - /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. - fn as_image_render_box(@self) -> @ImageRenderBox { - fail!("as_text_render_box() called on a non-text-render-box") - } + /// FIXME(pcwalton): This need not be stored in the box. + border: Slot>, - /// If this is a text render box, returns the underlying object. Fails otherwise. - /// - /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. - fn as_text_render_box(@self) -> @TextRenderBox { - fail!("as_text_render_box() called on a non-text-render-box") - } + /// The padding of the content box. + padding: Slot>, - /// If this is an unscanned text render box, returns the underlying object. Fails otherwise. - /// - /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. - fn as_unscanned_text_render_box(@self) -> @UnscannedTextRenderBox { - fail!("as_unscanned_text_render_box() called on a non-unscanned-text-render-box") - } + /// The margin of the content box. + margin: Slot>, - /// If this is an unscanned text render box, returns the underlying object. Fails otherwise. - /// - /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. - fn as_generic_render_box(@self) -> @GenericRenderBox { - fail!("as_generic_render_box() called on a generic-render-box") - } + /// The width of the content box. + content_box_width: Au, - /// Cleans up all memory associated with this render box. - fn teardown(&self) {} - - /// Returns true if this element is an unscanned text box that consists entirely of whitespace. - fn is_whitespace_only(&self) -> bool { - false - } - - /// 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, _: Au, _: bool) -> SplitBoxResult; - - /// Determines whether this box can merge with another box. - fn can_merge_with_box(&self, _: &RenderBox) -> bool { - false - } - - /// Returns the *minimum width* and *preferred width* of this render box as defined by CSS 2.1. - fn minimum_and_preferred_widths(&self) -> (Au, Au); - - fn box_height(&self) -> Au; - - /// Assigns the appropriate width. - fn assign_width(&self); - - fn debug_str(&self) -> ~str { - ~"???" - } + /// Info specific to the kind of box. Keep this enum small. + specific: SpecificBoxInfo, } -impl Clone for @RenderBox { - fn clone(&self) -> @RenderBox { - *self - } +/// Info specific to the kind of box. Keep this enum small. +pub enum SpecificBoxInfo { + GenericBox, + ImageBox(ImageBoxInfo), + ScannedTextBox(ScannedTextBoxInfo), + UnscannedTextBox(UnscannedTextBoxInfo), } -// FIXME(pcwalton): These are botches and can be removed once Rust gets trait fields. - -pub trait RenderBoxUtils { - fn base<'a>(&'a self) -> &'a RenderBoxBase; - - fn mut_base<'a>(&'a self) -> &'a mut RenderBoxBase; - - /// Returns true if this element is replaced content. This is true for images, form elements, - /// and so on. - fn is_replaced(&self) -> bool; - - /// Returns true if this element can be split. This is true for text boxes. - fn can_split(&self) -> bool; - - /// 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); - - /// 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); - - /// 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>, - absolute_bounds: &Rect); - - /// Adds the display items necessary to paint the borders of this render box to a display list - /// if necessary. - fn paint_borders_if_applicable( - &self, - list: &Cell>, - abs_bounds: &Rect); - - /// 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, - offset: &Point2D, - list: &Cell>); -} - -pub trait RenderBoxRefUtils<'self> { - fn base(self) -> &'self RenderBoxBase; - fn mut_base(self) -> &'self mut RenderBoxBase; -} - -/// A box that represents a generic render box. -pub struct GenericRenderBox { - base: RenderBoxBase, -} - -impl GenericRenderBox { - pub fn new(base: RenderBoxBase) -> GenericRenderBox { - GenericRenderBox { - base: base, - } - } - - fn need_clip(&self) -> bool { - if self.base.node.style().Box.overflow == overflow::hidden { - return true; - } - false - } -} - -impl RenderBox for GenericRenderBox { - fn class(&self) -> RenderBoxClass { - GenericRenderBoxClass - } - - fn as_generic_render_box(@self) -> @GenericRenderBox { - self - } - - fn minimum_and_preferred_widths(&self) -> (Au, Au) { - let guessed_width = self.base.guess_width(); - (guessed_width, guessed_width) - } - - fn split_to_width(@self, _: Au, _: bool) -> SplitBoxResult { - CannotSplit(self as @RenderBox) - } - - fn box_height(&self) -> Au { - Au::new(0) - } - - fn assign_width(&self) { - // FIXME(pcwalton): This seems clownshoes; can we remove? - self.base.position.mutate().ptr.size.width = Au::from_px(45) - } - - fn debug_str(&self) -> ~str { - ~"(generic)" - } -} - -/// A box that represents a (replaced content) image and its accompanying borders, shadows, etc. -pub struct ImageRenderBox { - base: RenderBoxBase, +/// A box that represents a replaced content image and its accompanying borders, shadows, etc. +pub struct ImageBoxInfo { + /// The image held within this box. image: Slot, } -impl ImageRenderBox { - #[inline] - pub fn new(base: RenderBoxBase, image_url: Url, local_image_cache: @mut LocalImageCache) - -> ImageRenderBox { - assert!(base.node.is_image_element()); - - ImageRenderBox { - base: base, +impl ImageBoxInfo { + /// Creates a new image box from the given URL and local image cache. + /// + /// FIXME(pcwalton): The fact that image boxes store the cache in the box makes little sense to + /// me. + pub fn new(image_url: Url, local_image_cache: @mut LocalImageCache) -> ImageBoxInfo { + ImageBoxInfo { image: Slot::init(ImageHolder::new(image_url, local_image_cache)), } } // Calculate the width of an image, accounting for the width attribute // TODO: This could probably go somewhere else - pub fn image_width(&self) -> Au { - let attr_width: Option = do self.base.node.with_imm_element |elt| { + pub fn image_width(&self, base: &Box) -> Au { + let attr_width: Option = do base.node.with_imm_element |elt| { match elt.get_attr("width") { Some(width) => { FromStr::from_str(width) @@ -281,8 +130,8 @@ impl ImageRenderBox { // Calculate the height of an image, accounting for the height attribute // TODO: This could probably go somewhere else - pub fn image_height(&self) -> Au { - let attr_height: Option = do self.base.node.with_imm_element |elt| { + pub fn image_height(&self, base: &Box) -> Au { + let attr_height: Option = do base.node.with_imm_element |elt| { match elt.get_attr("height") { Some(height) => { FromStr::from_str(height) @@ -302,381 +151,92 @@ impl ImageRenderBox { Au::from_px(px_height) } - - /// If this is an image render box, returns the underlying object. Fails otherwise. - /// - /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. - fn as_image_render_box(@self) -> @ImageRenderBox { - self - } - - fn debug_str(&self) -> ~str { - ~"(image)" - } } -impl RenderBox for ImageRenderBox { - fn class(&self) -> RenderBoxClass { - ImageRenderBoxClass - } - - fn split_to_width(@self, _: Au, _: bool) -> SplitBoxResult { - CannotSplit(self as @RenderBox) - } - - fn minimum_and_preferred_widths(&self) -> (Au, Au) { - let guessed_width = self.base.guess_width(); - let image_width = self.image_width(); - (guessed_width + image_width, guessed_width + image_width) - } - - fn box_height(&self) -> Au { - let size = self.image.mutate().ptr.get_size(); - let height = Au::from_px(size.unwrap_or(Size2D(0, 0)).height); - self.base.position.mutate().ptr.size.height = height; - debug!("box_height: found image height: {}", height); - height - } - - fn assign_width(&self) { - let width = self.image_width(); - self.base.position.mutate().ptr.size.width = width; - } - - /// If this is an image render box, returns the underlying object. Fails otherwise. - /// - /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. - fn as_image_render_box(@self) -> @ImageRenderBox { - self - } -} - -/// A box representing a single run of text with a distinct style. A `TextRenderBox` may be split -/// into two or more 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, +/// A scanned text box represents a single run of text with a distinct style. A `TextBox` may be +/// split into two or more 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 single `TextRun` +/// object. +pub struct ScannedTextBoxInfo { + /// The text run that this represents. run: @TextRun, + + /// The range within the above text run that this represents. range: Range, } -impl TextRenderBox { - /// Creates a TextRenderBox from a base render box, a range, and a text run. The size of the - /// the base render box is ignored and becomes the size of the text run. - /// - /// FIXME(pcwalton): This API is confusing. - pub fn new(base: RenderBoxBase, run: @TextRun, range: Range) -> TextRenderBox { - debug!("Creating textbox with span: (strlen={:u}, off={:u}, len={:u}) of textrun ({:s}) (len={:u})", - run.char_len(), - range.begin(), - range.length(), - *run.text.get(), - run.char_len()); - - assert!(range.begin() < run.char_len()); - assert!(range.end() <= run.char_len()); - assert!(range.length() > 0); - - let metrics = run.metrics_for_range(&range); - - // FIXME(pcwalton): This block is necessary due to Rust #6248. If we don't have it, then - // the "currently borrowed" flag will be moved before the destructor runs, causing a - // (harmless) undefined memory write and a (very harmful) sticking of `position` in the - // "mutably borrowed" state, which will cause failures later. - { - base.position.mutate().ptr.size = metrics.bounding_box.size; - } - - TextRenderBox { - base: base, +impl ScannedTextBoxInfo { + /// Creates the information specific to a scanned text box from a range and a text run. + pub fn new(run: @TextRun, range: Range) -> ScannedTextBoxInfo { + ScannedTextBoxInfo { run: run, range: range, } } } -impl RenderBox for TextRenderBox { - fn class(&self) -> RenderBoxClass { - TextRenderBoxClass - } - - fn as_text_render_box(@self) -> @TextRenderBox { - self - } - - fn teardown(&self) { - self.run.teardown(); - } - - fn minimum_and_preferred_widths(&self) -> (Au, Au) { - let guessed_width = self.base.guess_width(); - let min_width = self.run.min_width_for_range(&self.range); - - let mut max_line_width = Au::new(0); - for line_range in self.run.iter_natural_lines_for_range(&self.range) { - let line_metrics = self.run.metrics_for_range(&line_range); - max_line_width = Au::max(max_line_width, line_metrics.advance_width); - } - - (guessed_width + min_width, guessed_width + max_line_width) - } - - fn box_height(&self) -> Au { - let range = &self.range; - let run = &self.run; - - // Compute the height based on the line-height and font size - let text_bounds = run.metrics_for_range(range).bounding_box; - let em_size = text_bounds.size.height; - let line_height = self.base.calculate_line_height(em_size); - - line_height - } - - fn assign_width(&self) { - // Text boxes are preinitialized. - } - - /// 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, max_width: Au, starts_line: bool) -> SplitBoxResult { - let mut pieces_processed_count: uint = 0; - let mut remaining_width: Au = max_width; - let mut left_range = Range::new(self.range.begin(), 0); - let mut right_range: Option = None; - - debug!("split_to_width: splitting text box (strlen={:u}, range={}, avail_width={})", - self.run.text.get().len(), - self.range, - max_width); - - for (glyphs, offset, slice_range) in self.run.iter_slices_for_range(&self.range) { - debug!("split_to_width: considering slice (offset={}, range={}, remain_width={})", - offset, - slice_range, - remaining_width); - - let metrics = self.run.metrics_for_slice(glyphs, &slice_range); - let advance = metrics.advance_width; - let should_continue: bool; - - if advance <= remaining_width { - should_continue = true; - - if starts_line && pieces_processed_count == 0 && glyphs.is_whitespace() { - debug!("split_to_width: case=skipping leading trimmable whitespace"); - left_range.shift_by(slice_range.length() as int); - } else { - debug!("split_to_width: case=enlarging span"); - remaining_width = remaining_width - advance; - left_range.extend_by(slice_range.length() as int); - } - } else { // The advance is more than the remaining width. - should_continue = false; - let slice_begin = offset + slice_range.begin(); - let slice_end = offset + slice_range.end(); - - if glyphs.is_whitespace() { - // If there are still things after the trimmable whitespace, create the - // right chunk. - if slice_end < self.range.end() { - debug!("split_to_width: case=skipping trimmable trailing \ - whitespace, then split remainder"); - let right_range_end = self.range.end() - slice_end; - right_range = Some(Range::new(slice_end, right_range_end)); - } else { - debug!("split_to_width: case=skipping trimmable trailing \ - whitespace"); - } - } else if slice_begin < self.range.end() { - // There are still some things left over at the end of the line. Create - // the right chunk. - let right_range_end = self.range.end() - slice_begin; - right_range = Some(Range::new(slice_begin, right_range_end)); - debug!("split_to_width: case=splitting remainder with right range={:?}", - right_range); - } - } - - pieces_processed_count += 1; - - if !should_continue { - break - } - } - - let left_box = if left_range.length() > 0 { - let new_text_box = @TextRenderBox::new(self.base.clone(), self.run, left_range); - Some(new_text_box as @RenderBox) - } else { - None - }; - - let right_box = do right_range.map_default(None) |range: Range| { - let new_text_box = @TextRenderBox::new(self.base.clone(), self.run, range); - Some(new_text_box as @RenderBox) - }; - - if pieces_processed_count == 1 || left_box.is_none() { - SplitDidNotFit(left_box, right_box) - } else { - SplitDidFit(left_box, right_box) - } - } - - fn debug_str(&self) -> ~str { - self.run.text.get().to_str() - } -} - -/// The data for an unscanned text box. -pub struct UnscannedTextRenderBox { - base: RenderBoxBase, +/// Data for an unscanned text box. Unscanned text boxes are the results of flow construction that +/// have not yet had their width determined. +pub struct UnscannedTextBoxInfo { + /// The text inside the box. text: ~str, - - // Cache font-style and text-decoration to check whether - // this box can merge with another render box. - font_style: Option, - text_decoration: Option, } -impl UnscannedTextRenderBox { - /// Creates a new instance of `UnscannedTextRenderBox`. - #[inline(always)] - 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.clone(), +impl UnscannedTextBoxInfo { + /// Creates a new instance of `UnscannedTextBoxInfo` from the given DOM node. + pub fn new(node: &AbstractNode) -> UnscannedTextBoxInfo { + node.with_imm_text(|text_node| { + // FIXME(pcwalton): Don't copy text; atomically reference count it instead. + UnscannedTextBoxInfo { text: text_node.element.data.to_str(), - font_style: None, - text_decoration: None, } - } + }) } - - /// Copies out the text from an unscanned text box. - pub fn raw_text(&self) -> ~str { - self.text.clone() - } -} - -impl RenderBox for UnscannedTextRenderBox { - fn class(&self) -> RenderBoxClass { - UnscannedTextRenderBoxClass - } - - fn is_whitespace_only(&self) -> bool { - self.text.is_whitespace() - } - - fn can_merge_with_box(&self, other: &RenderBox) -> bool { - if other.class() == UnscannedTextRenderBoxClass { - let this_base = &self.base; - let other_base = other.base(); - return this_base.font_style() == other_base.font_style() && - this_base.text_decoration() == other_base.text_decoration() - } - false - } - - fn box_height(&self) -> Au { - fail!("can't get height of unscanned text box") - } - - /// 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, _: Au, _: bool) -> SplitBoxResult { - fail!("WAT: shouldn't be an unscanned text box here.") - } - - /// Returns the *minimum width* and *preferred width* of this render box as defined by CSS 2.1. - fn minimum_and_preferred_widths(&self) -> (Au, Au) { - fail!("WAT: shouldn't be an unscanned text box here.") - } - - fn assign_width(&self) { - fail!("WAT: shouldn't be an unscanned text box here.") - } - - /// If this is an unscanned text render box, returns the underlying object. Fails otherwise. - /// - /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. - fn as_unscanned_text_render_box(@self) -> @UnscannedTextRenderBox { - self - } - - fn debug_str(&self) -> ~str { - self.text.clone() - } -} - -#[deriving(Eq)] -pub enum RenderBoxClass { - GenericRenderBoxClass, - ImageRenderBoxClass, - TextRenderBoxClass, - UnscannedTextRenderBoxClass, } /// Represents the outcome of attempting to split a box. pub enum SplitBoxResult { - CannotSplit(@RenderBox), + CannotSplit(@Box), // in general, when splitting the left or right side can // be zero length, due to leading/trailing trimmable whitespace - SplitDidFit(Option<@RenderBox>, Option<@RenderBox>), - SplitDidNotFit(Option<@RenderBox>, Option<@RenderBox>) + SplitDidFit(Option<@Box>, Option<@Box>), + SplitDidNotFit(Option<@Box>, Option<@Box>) } -/// Data common to all boxes. -#[deriving(Clone)] -pub struct RenderBoxBase { - /// The DOM node that this `RenderBox` originates from. - node: AbstractNode, - - /// The position of this box relative to its owning flow. - position: Slot>, - - /// A debug ID. - /// - /// TODO(#87) Make this only present in debug builds. - id: int, - - /// the border of the content box. - border: SideOffsets2D, - - /// the padding of the content box. - padding: SideOffsets2D, - - /// the margin of the content box. - margin: SideOffsets2D, - - /// The width of the content box. - content_box_width: Au, -} - -impl RenderBoxBase { - /// Constructs a new `RenderBoxBase` instance. - pub fn new(node: AbstractNode, id: int) - -> RenderBoxBase { - RenderBoxBase { +impl Box { + /// Constructs a new `Box` instance. + pub fn new(node: AbstractNode, specific: SpecificBoxInfo) -> Box { + Box { node: node, position: Slot::init(Au::zero_rect()), - id: id, - border: Zero::zero(), - padding: Zero::zero(), - margin: Zero::zero(), + border: Slot::init(Zero::zero()), + padding: Slot::init(Zero::zero()), + margin: Slot::init(Zero::zero()), content_box_width: Zero::zero(), + specific: specific, } } - pub fn id(&self) -> int { - 0 + /// Returns a debug ID of this box. This ID should not be considered stable across multiple + /// layouts or box manipulations. + pub fn debug_id(&self) -> uint { + unsafe { + cast::transmute(self) + } + } + + /// Transforms this box into another box of the given type, with the given size, preserving all + /// the other data. + pub fn transform(&self, size: Size2D, specific: SpecificBoxInfo) -> Box { + Box { + node: self.node, + position: Slot::init(Rect(self.position.get().origin, size)), + border: Slot::init(self.border.get()), + padding: Slot::init(self.padding.get()), + margin: Slot::init(self.margin.get()), + content_box_width: self.content_box_width, + specific: specific, + } } fn guess_width(&self) -> Au { @@ -694,8 +254,13 @@ impl RenderBoxBase { let padding_left = self.compute_padding_length(style.Padding.padding_left, Au::new(0)); let padding_right = self.compute_padding_length(style.Padding.padding_right, Au::new(0)); - width + margin_left + margin_right + padding_left + padding_right + self.border.left + - self.border.right + width + margin_left + margin_right + padding_left + padding_right + + self.border.get().left + self.border.get().right + } + + /// Sets the size of this box. + fn set_size(&self, new_size: Size2D) { + self.position.set(Rect(self.position.get().origin, new_size)) } pub fn calculate_line_height(&self, font_size: Au) -> Au { @@ -707,38 +272,43 @@ impl RenderBoxBase { } /// Populates the box model border parameters from the given computed style. - pub fn compute_borders(&mut self, style: &ComputedValues) { - self.border.top = style.Border.border_top_width; - self.border.right = style.Border.border_right_width; - self.border.bottom = style.Border.border_bottom_width; - self.border.left = style.Border.border_left_width; + /// + /// FIXME(pcwalton): This should not be necessary. Just go to the style. + pub fn compute_borders(&self, style: &ComputedValues) { + self.border.set(SideOffsets2D::new(style.Border.border_top_width, + style.Border.border_right_width, + style.Border.border_bottom_width, + style.Border.border_left_width)) } /// Populates the box model padding parameters from the given computed style. - pub fn compute_padding(&mut self, style: &ComputedValues, containing_block_width: Au) { - self.padding.top = self.compute_padding_length(style.Padding.padding_top, - containing_block_width); - self.padding.right = self.compute_padding_length(style.Padding.padding_right, - containing_block_width); - self.padding.bottom = self.compute_padding_length(style.Padding.padding_bottom, - containing_block_width); - self.padding.left = self.compute_padding_length(style.Padding.padding_left, - containing_block_width); + pub fn compute_padding(&self, style: &ComputedValues, containing_block_width: Au) { + let padding = SideOffsets2D::new(self.compute_padding_length(style.Padding.padding_top, + containing_block_width), + self.compute_padding_length(style.Padding.padding_right, + containing_block_width), + self.compute_padding_length(style.Padding.padding_bottom, + containing_block_width), + self.compute_padding_length(style.Padding.padding_left, + containing_block_width)); + self.padding.set(padding) } - pub fn compute_padding_length(&self, padding: LengthOrPercentage, content_box_width: Au) -> Au { + pub fn compute_padding_length(&self, padding: LengthOrPercentage, content_box_width: Au) + -> Au { specified(padding, content_box_width) } pub fn noncontent_width(&self) -> Au { - let left = self.margin.left + self.border.left + self.padding.left; - let right = self.margin.right + self.border.right + self.padding.right; + let left = self.margin.get().left + self.border.get().left + self.padding.get().left; + let right = self.margin.get().right + self.border.get().right + self.padding.get().right; left + right } pub fn noncontent_height(&self) -> Au { - let top = self.margin.top + self.border.top + self.padding.top; - let bottom = self.margin.bottom + self.border.bottom + self.padding.bottom; + let top = self.margin.get().top + self.border.get().top + self.padding.get().top; + let bottom = self.margin.get().bottom + self.border.get().bottom + + self.padding.get().bottom; top + bottom } @@ -746,9 +316,10 @@ impl RenderBoxBase { /// the owning flow. pub fn content_box(&self) -> Rect { let position = self.position.get(); - let origin = Point2D(position.origin.x + self.border.left + self.padding.left, + let origin = Point2D(position.origin.x + self.border.get().left + self.padding.get().left, position.origin.y); - let noncontent_width = self.border.left + self.padding.left + self.border.right + self.padding.right; + let noncontent_width = self.border.get().left + self.padding.get().left + + self.border.get().right + self.padding.get().right; let size = Size2D(position.size.width - noncontent_width, position.size.height); Rect(origin, size) } @@ -767,8 +338,7 @@ impl RenderBoxBase { self.content_box() } - /// Returns the nearest ancestor-or-self `Element` to the DOM node that this render box - /// represents. + /// Returns the nearest ancestor-or-self `Element` to the DOM node that this box represents. /// /// If there is no ancestor-or-self `Element` node, fails. pub fn nearest_ancestor_element(&self) -> AbstractNode { @@ -782,7 +352,9 @@ impl RenderBoxBase { node } - // Always inline for SCCP. + /// Always inline for SCCP. + /// + /// FIXME(pcwalton): Just replace with the clear type from the style module for speed? #[inline(always)] pub fn clear(&self) -> Option { let style = self.node.style(); @@ -795,6 +367,9 @@ impl RenderBoxBase { } /// Converts this node's computed style to a font style used for rendering. + /// + /// FIXME(pcwalton): This should not be necessary; just make the font part of style sharable + /// with the display list somehow. (Perhaps we should use an ARC.) pub fn font_style(&self) -> FontStyle { let my_style = self.nearest_ancestor_element().style(); @@ -877,101 +452,91 @@ impl RenderBoxBase { None => text_decoration::none, Some(parent) => get_propagated_text_decoration(parent), } - } - else { + } else { text_decoration } } get_propagated_text_decoration(self.nearest_ancestor_element()) } + /// Returns the sum of margin, border, and padding on the left. pub fn offset(&self) -> Au { - self.margin.left + self.border.left + self.padding.left + self.margin.get().left + self.border.get().left + self.padding.get().left } -} - -impl RenderBoxUtils for @RenderBox { - #[inline(always)] - fn base<'a>(&'a self) -> &'a RenderBoxBase { - unsafe { - let (_, box_ptr): (uint, *Box) = cast::transmute(*self); - cast::transmute(&(*box_ptr).data) + /// Returns true if this element is replaced content. This is true for images, form elements, + /// and so on. + pub fn is_replaced(&self) -> bool { + match self.specific { + ImageBox(*) => true, + _ => false, } } - fn mut_base<'a>(&'a self) -> &'a mut RenderBoxBase { - unsafe { - let (_, box_ptr): (uint, *Box) = cast::transmute(*self); - cast::transmute_mut(&(*box_ptr).data) + /// Returns true if this element can be split. This is true for text boxes. + pub fn can_split(&self) -> bool { + match self.specific { + ScannedTextBox(*) => true, + _ => false, } } - fn is_replaced(&self) -> bool { - self.class() == ImageRenderBoxClass - } - - fn can_split(&self) -> bool { - self.class() == TextRenderBoxClass - } - /// 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) { + pub fn get_used_width(&self) -> (Au, Au) { // TODO: This should actually do some computation! See CSS 2.1, Sections 10.3 and 10.4. (Au::new(0), Au::new(0)) } /// 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) { + pub fn get_used_height(&self) -> (Au, Au) { // TODO: This should actually do some computation! See CSS 2.1, Sections 10.5 and 10.6. (Au::new(0), Au::new(0)) } - /// 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>, - absolute_bounds: &Rect) { + /// Adds the display items necessary to paint the background of this box to the display list if + /// necessary. + pub fn paint_background_if_applicable( + @self, + list: &Cell>, + absolute_bounds: &Rect) { // 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 render box". - let nearest_ancestor_element = self.base().nearest_ancestor_element(); + // doesn't have a box". + let nearest_ancestor_element = self.nearest_ancestor_element(); let style = nearest_ancestor_element.style(); let background_color = style.resolve_color(style.Background.background_color); if !background_color.alpha.approx_eq(&0.0) { - do list.with_mut_ref |list| { + list.with_mut_ref(|list| { let solid_color_display_item = ~SolidColorDisplayItem { base: BaseDisplayItem { bounds: *absolute_bounds, - extra: ExtraDisplayListData::new(self), + extra: ExtraDisplayListData::new(&self), }, color: background_color.to_gfx_color(), }; list.append_item(SolidColorDisplayItemClass(solid_color_display_item)) - } + }) } } - /// Adds the display items necessary to paint the borders of this render box to a display list - /// if necessary. - fn paint_borders_if_applicable( - &self, - list: &Cell>, - abs_bounds: &Rect) { + /// Adds the display items necessary to paint the borders of this box to a display list if + /// necessary. + pub fn paint_borders_if_applicable( + @self, + list: &Cell>, + abs_bounds: &Rect) { // Fast path. - let base = self.base(); - let border = base.border; + let border = self.border.get(); if border.is_zero() { return } - let style = base.style(); + let style = self.style(); let top_color = style.resolve_color(style.Border.border_top_color); let right_color = style.resolve_color(style.Border.border_right_color); let bottom_color = style.resolve_color(style.Border.border_bottom_color); @@ -986,12 +551,9 @@ impl RenderBoxUtils for @RenderBox { let border_display_item = ~BorderDisplayItem { base: BaseDisplayItem { bounds: *abs_bounds, - extra: ExtraDisplayListData::new(self), + extra: ExtraDisplayListData::new(&self), }, - border: SideOffsets2D::new(border.top, - border.right, - border.bottom, - border.left), + border: border, color: SideOffsets2D::new(top_color.to_gfx_color(), right_color.to_gfx_color(), bottom_color.to_gfx_color(), @@ -1006,7 +568,7 @@ impl RenderBoxUtils for @RenderBox { } } - /// Adds the display items for this render box to the given display list. + /// Adds the display items for this box to the given display list. /// /// Arguments: /// * `builder`: The display list builder, which manages the coordinate system and options. @@ -1020,43 +582,40 @@ impl RenderBoxUtils for @RenderBox { /// 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, - offset: &Point2D, - list: &Cell>) { - let base = self.base(); - let box_bounds = base.position.get(); + pub fn build_display_list( + @self, + _: &DisplayListBuilder, + dirty: &Rect, + offset: &Point2D, + list: &Cell>) { + let box_bounds = self.position.get(); let absolute_box_bounds = box_bounds.translate(offset); - debug!("RenderBox::build_display_list at rel={}, abs={}: {:s}", + debug!("Box::build_display_list at rel={}, abs={}: {:s}", box_bounds, absolute_box_bounds, self.debug_str()); - debug!("RenderBox::build_display_list: dirty={}, offset={}", *dirty, *offset); + debug!("Box::build_display_list: dirty={}, offset={}", *dirty, *offset); - if base.nearest_ancestor_element().style().Box.visibility != visibility::visible { + if self.nearest_ancestor_element().style().Box.visibility != visibility::visible { return; } if absolute_box_bounds.intersects(dirty) { - debug!("RenderBox::build_display_list: intersected. Adding display item..."); + debug!("Box::build_display_list: intersected. Adding display item..."); } else { - debug!("RenderBox::build_display_list: Did not intersect..."); + debug!("Box::build_display_list: Did not intersect..."); return; } - match self.class() { - UnscannedTextRenderBoxClass => fail!("Shouldn't see unscanned boxes here."), - TextRenderBoxClass => { - let text_box = self.as_text_render_box(); - - // Add the background to the list, if applicable. - self.paint_background_if_applicable(list, &absolute_box_bounds); + // Add the background to the list, if applicable. + self.paint_background_if_applicable(list, &absolute_box_bounds); + match self.specific { + UnscannedTextBox(_) => fail!("Shouldn't see unscanned boxes here."), + ScannedTextBox(ref text_box) => { do list.with_mut_ref |list| { let item = ~ClipDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(self), + extra: ExtraDisplayListData::new(&self), }, child_list: ~[], need_clip: false @@ -1065,7 +624,7 @@ impl RenderBoxUtils for @RenderBox { } - let nearest_ancestor_element = base.nearest_ancestor_element(); + let nearest_ancestor_element = self.nearest_ancestor_element(); let color = nearest_ancestor_element.style().Color.color.to_gfx_color(); // Create the text box. @@ -1073,7 +632,7 @@ impl RenderBoxUtils for @RenderBox { let text_display_item = ~TextDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(self), + extra: ExtraDisplayListData::new(&self), }, // FIXME(pcwalton): Allocation? Why?! text_run: ~text_box.run.serialize(), @@ -1096,7 +655,7 @@ impl RenderBoxUtils for @RenderBox { let border_display_item = ~BorderDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(self), + extra: ExtraDisplayListData::new(&self), }, border: debug_border, color: SideOffsets2D::new_all_same(rgb(0, 0, 200)), @@ -1118,7 +677,7 @@ impl RenderBoxUtils for @RenderBox { let border_display_item = ~BorderDisplayItem { base: BaseDisplayItem { bounds: baseline, - extra: ExtraDisplayListData::new(self), + extra: ExtraDisplayListData::new(&self), }, border: debug_border, color: SideOffsets2D::new_all_same(rgb(0, 200, 0)), @@ -1131,19 +690,15 @@ impl RenderBoxUtils for @RenderBox { () }); }, - GenericRenderBoxClass => { - // Add the background to the list, if applicable. - self.paint_background_if_applicable(list, &absolute_box_bounds); - - let generic_box = self.as_generic_render_box(); + GenericBox => { do list.with_mut_ref |list| { let item = ~ClipDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(self), + extra: ExtraDisplayListData::new(&self), }, child_list: ~[], - need_clip: generic_box.need_clip() + need_clip: self.needs_clip() }; list.append_item(ClipDisplayItemClass(item)); } @@ -1157,7 +712,7 @@ impl RenderBoxUtils for @RenderBox { let border_display_item = ~BorderDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(self), + extra: ExtraDisplayListData::new(&self), }, border: debug_border, color: SideOffsets2D::new_all_same(rgb(0, 0, 200)), @@ -1170,15 +725,12 @@ impl RenderBoxUtils for @RenderBox { () }); }, - ImageRenderBoxClass => { - // Add the background to the list, if applicable. - self.paint_background_if_applicable(list, &absolute_box_bounds); - + ImageBox(ref image_box) => { do list.with_mut_ref |list| { let item = ~ClipDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(self), + extra: ExtraDisplayListData::new(&self), }, child_list: ~[], need_clip: false @@ -1186,8 +738,6 @@ impl RenderBoxUtils for @RenderBox { list.append_item(ClipDisplayItemClass(item)); } - let image_box = self.as_image_render_box(); - match image_box.image.mutate().ptr.get_image() { Some(image) => { debug!("(building display list) building image box"); @@ -1197,7 +747,7 @@ impl RenderBoxUtils for @RenderBox { let image_display_item = ~ImageDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(self), + extra: ExtraDisplayListData::new(&self), }, image: image.clone(), }; @@ -1219,23 +769,218 @@ impl RenderBoxUtils for @RenderBox { // TODO: Outlines. self.paint_borders_if_applicable(list, &absolute_box_bounds); } -} -impl<'self> RenderBoxRefUtils<'self> for &'self RenderBox { - #[inline(always)] - fn base(self) -> &'self RenderBoxBase { - unsafe { - let (_, box_ptr): (uint, *RenderBoxBase) = cast::transmute(self); - cast::transmute(box_ptr) + /// Returns the *minimum width* and *preferred width* of this box as defined by CSS 2.1. + pub fn minimum_and_preferred_widths(&self) -> (Au, Au) { + let guessed_width = self.guess_width(); + let (additional_minimum, additional_preferred) = match self.specific { + GenericBox => (Au(0), Au(0)), + ImageBox(ref image_box_info) => { + let image_width = image_box_info.image_width(self); + (image_width, image_width) + } + ScannedTextBox(ref text_box_info) => { + let range = &text_box_info.range; + let min_line_width = text_box_info.run.min_width_for_range(range); + + let mut max_line_width = Au::new(0); + for line_range in text_box_info.run.iter_natural_lines_for_range(range) { + let line_metrics = text_box_info.run.metrics_for_range(&line_range); + max_line_width = Au::max(max_line_width, line_metrics.advance_width); + } + + (min_line_width, max_line_width) + } + UnscannedTextBox(*) => fail!("Unscanned text boxes should have been scanned by now!"), + }; + (guessed_width + additional_minimum, guessed_width + additional_preferred) + } + + /// Returns, and computes, the height of this box. + /// + /// FIXME(pcwalton): Rename to just `height`? + /// FIXME(pcwalton): This function *mutates* the height? Gross! Refactor please. + pub fn box_height(&self) -> Au { + match self.specific { + GenericBox => Au(0), + ImageBox(ref image_box_info) => { + let size = image_box_info.image.mutate().ptr.get_size(); + let height = Au::from_px(size.unwrap_or(Size2D(0, 0)).height); + + // Eww. Refactor this. + self.position.mutate().ptr.size.height = height; + debug!("box_height: found image height: {}", height); + + height + } + ScannedTextBox(ref text_box_info) => { + // Compute the height based on the line-height and font size. + let (range, run) = (&text_box_info.range, &text_box_info.run); + let text_bounds = run.metrics_for_range(range).bounding_box; + let em_size = text_bounds.size.height; + self.calculate_line_height(em_size) + } + UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"), } } - #[inline(always)] - fn mut_base(self) -> &'self mut RenderBoxBase { - unsafe { - let (_, box_ptr): (uint, *mut RenderBoxBase) = cast::transmute(self); - cast::transmute(box_ptr) + /// Attempts to split this box so that its width is no more than `max_width`. + pub fn split_to_width(@self, max_width: Au, starts_line: bool) -> SplitBoxResult { + match self.specific { + GenericBox | ImageBox(_) => CannotSplit(self), + UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"), + ScannedTextBox(ref text_box_info) => { + let mut pieces_processed_count: uint = 0; + let mut remaining_width: Au = max_width; + let mut left_range = Range::new(text_box_info.range.begin(), 0); + let mut right_range: Option = None; + + debug!("split_to_width: splitting text box (strlen={:u}, range={}, \ + avail_width={})", + text_box_info.run.text.get().len(), + text_box_info.range, + max_width); + + for (glyphs, offset, slice_range) in text_box_info.run.iter_slices_for_range( + &text_box_info.range) { + debug!("split_to_width: considering slice (offset={}, range={}, \ + remain_width={})", + offset, + slice_range, + remaining_width); + + let metrics = text_box_info.run.metrics_for_slice(glyphs, &slice_range); + let advance = metrics.advance_width; + + let should_continue; + if advance <= remaining_width { + should_continue = true; + + if starts_line && pieces_processed_count == 0 && glyphs.is_whitespace() { + debug!("split_to_width: case=skipping leading trimmable whitespace"); + left_range.shift_by(slice_range.length() as int); + } else { + debug!("split_to_width: case=enlarging span"); + remaining_width = remaining_width - advance; + left_range.extend_by(slice_range.length() as int); + } + } else { + // The advance is more than the remaining width. + should_continue = false; + let slice_begin = offset + slice_range.begin(); + let slice_end = offset + slice_range.end(); + + if glyphs.is_whitespace() { + // If there are still things after the trimmable whitespace, create the + // right chunk. + if slice_end < text_box_info.range.end() { + debug!("split_to_width: case=skipping trimmable trailing \ + whitespace, then split remainder"); + let right_range_end = text_box_info.range.end() - slice_end; + right_range = Some(Range::new(slice_end, right_range_end)); + } else { + debug!("split_to_width: case=skipping trimmable trailing \ + whitespace"); + } + } else if slice_begin < text_box_info.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_info.range.end() - slice_begin; + right_range = Some(Range::new(slice_begin, right_range_end)); + debug!("split_to_width: case=splitting remainder with right range={:?}", + right_range); + } + } + + pieces_processed_count += 1; + + if !should_continue { + break + } + } + + let left_box = if left_range.length() > 0 { + let new_text_box_info = ScannedTextBoxInfo::new(text_box_info.run, left_range); + let new_text_box = @Box::new(self.node, ScannedTextBox(new_text_box_info)); + let new_metrics = new_text_box_info.run.metrics_for_range(&left_range); + new_text_box.set_size(new_metrics.bounding_box.size); + Some(new_text_box) + } else { + None + }; + + let right_box = right_range.map_default(None, |range: Range| { + let new_text_box_info = ScannedTextBoxInfo::new(text_box_info.run, range); + let new_text_box = @Box::new(self.node, ScannedTextBox(new_text_box_info)); + let new_metrics = new_text_box_info.run.metrics_for_range(&range); + new_text_box.set_size(new_metrics.bounding_box.size); + Some(new_text_box) + }); + + if pieces_processed_count == 1 || left_box.is_none() { + SplitDidNotFit(left_box, right_box) + } else { + SplitDidFit(left_box, right_box) + } + } } } + + /// Returns true if this box is an unscanned text box that consists entirely of whitespace. + pub fn is_whitespace_only(&self) -> bool { + match self.specific { + UnscannedTextBox(ref text_box_info) => text_box_info.text.is_whitespace(), + _ => false, + } + } + + /// Assigns the appropriate width to this box. + pub fn assign_width(&self) { + match self.specific { + GenericBox => { + // FIXME(pcwalton): This seems clownshoes; can we remove? + self.position.mutate().ptr.size.width = Au::from_px(45) + } + ImageBox(ref image_box_info) => { + let image_width = image_box_info.image_width(self); + self.position.mutate().ptr.size.width = image_width + } + ScannedTextBox(_) => { + // Scanned text boxes will have already had their widths assigned by this point. + } + UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"), + } + } + + /// Returns true if this box can merge with another adjacent box or false otherwise. + pub fn can_merge_with_box(&self, other: &Box) -> bool { + match (&self.specific, &other.specific) { + (&UnscannedTextBox(_), &UnscannedTextBox(_)) => { + self.font_style() == other.font_style() && + self.text_decoration() == other.text_decoration() + } + _ => false, + } + } + + /// Cleans up all the memory associated with this box. + pub fn teardown(&self) { + match self.specific { + ScannedTextBox(ref text_box_info) => text_box_info.run.teardown(), + _ => {} + } + } + + /// Returns true if the contents should be clipped (i.e. if `overflow` is `hidden`). + pub fn needs_clip(&self) -> bool { + self.node.style().Box.overflow == overflow::hidden + } + + /// Returns a debugging string describing this box. + /// + /// TODO(pcwalton): Reimplement. + pub fn debug_str(&self) -> ~str { + ~"(Box)" + } } diff --git a/src/components/main/layout/construct.rs b/src/components/main/layout/construct.rs index 9341727e227..263a8409e23 100644 --- a/src/components/main/layout/construct.rs +++ b/src/components/main/layout/construct.rs @@ -7,8 +7,8 @@ //! Each step of the traversal considers the node and existing flow, if there is one. If a node is //! not dirty and an existing flow exists, then the traversal reuses that flow. Otherwise, it //! proceeds to construct either a flow or a `ConstructionItem`. A construction item is a piece of -//! intermediate data that goes with a DOM node and hasn't found its "home" yet—maybe it's a render -//! box, maybe it's an absolute or fixed position thing that hasn't found its containing block yet. +//! intermediate data that goes with a DOM node and hasn't found its "home" yet—maybe it's a box, +//! maybe it's an absolute or fixed position thing that hasn't found its containing block yet. //! Construction items bubble up the tree from children to parents until they find their homes. //! //! TODO(pcwalton): There is no incremental reflow yet. This scheme requires that nodes either have @@ -22,8 +22,7 @@ use css::node_style::StyledNode; use layout::block::BlockFlow; -use layout::box::{GenericRenderBox, ImageRenderBox, RenderBox, RenderBoxBase}; -use layout::box::{UnscannedTextRenderBox}; +use layout::box::{Box, GenericBox, ImageBox, ImageBoxInfo, UnscannedTextBox, UnscannedTextBoxInfo}; use layout::context::LayoutContext; use layout::float_context::FloatType; use layout::flow::{Flow, FlowData, MutableFlowUtils}; @@ -70,8 +69,8 @@ struct InlineBoxesConstructionResult { /// TODO(pcwalton): Small vector optimization. splits: Option<~[InlineBlockSplit]>, - /// Any render boxes that succeed the {ib} splits. - boxes: ~[@RenderBox], + /// Any boxes that succeed the {ib} splits. + boxes: ~[@Box], } /// Represents an {ib} split that has not yet found the containing block that it belongs to. This @@ -97,10 +96,10 @@ struct InlineBoxesConstructionResult { /// C /// ]) struct InlineBlockSplit { - /// The inline render boxes that precede the flow. + /// The inline boxes that precede the flow. /// /// TODO(pcwalton): Small vector optimization. - predecessor_boxes: ~[@RenderBox], + predecessor_boxes: ~[@Box], /// The flow that caused this {ib} split. flow: ~Flow:, @@ -180,11 +179,6 @@ pub struct FlowConstructor<'self> { /// /// FIXME(pcwalton): This is going to have to be atomic; can't we do something better? next_flow_id: Slot, - - /// The next box ID to assign. - /// - /// FIXME(pcwalton): This is going to have to be atomic; can't we do something better? - next_box_id: Slot, } impl<'self> FlowConstructor<'self> { @@ -193,7 +187,6 @@ impl<'self> FlowConstructor<'self> { FlowConstructor { layout_context: layout_context, next_flow_id: Slot::init(0), - next_box_id: Slot::init(0), } } @@ -204,39 +197,36 @@ impl<'self> FlowConstructor<'self> { id } - /// Returns the next render box ID and bumps the internal counter. - fn next_box_id(&self) -> int { - let id = self.next_box_id.get(); - self.next_box_id.set(id + 1); - id - } - - /// Builds a `RenderBox` for the given image. This is out of line to guide inlining. - fn build_box_for_image(&self, base: RenderBoxBase, node: AbstractNode) - -> @RenderBox { + /// Builds the `ImageBoxInfo` for the given image. This is out of line to guide inlining. + fn build_box_info_for_image(&self, node: AbstractNode) -> Option { // FIXME(pcwalton): Don't copy URLs. let url = node.with_imm_image_element(|image_element| { image_element.image.as_ref().map(|url| (*url).clone()) }); match url { - None => @GenericRenderBox::new(base) as @RenderBox, + None => None, Some(url) => { - // FIXME(pcwalton): The fact that image render boxes store the cache in the - // box makes little sense to me. - @ImageRenderBox::new(base, url, self.layout_context.image_cache) as @RenderBox + // FIXME(pcwalton): The fact that image boxes store the cache within them makes + // little sense to me. + Some(ImageBoxInfo::new(url, self.layout_context.image_cache)) } } } - /// Builds a `RenderBox` for the given node. - fn build_box_for_node(&self, node: AbstractNode) -> @RenderBox { - let base = RenderBoxBase::new(node, self.next_box_id()); - match node.type_id() { - ElementNodeTypeId(HTMLImageElementTypeId) => self.build_box_for_image(base, node), - TextNodeTypeId => @UnscannedTextRenderBox::new(base) as @RenderBox, - _ => @GenericRenderBox::new(base) as @RenderBox, - } + /// Builds a `Box` for the given node. + fn build_box_for_node(&self, node: AbstractNode) -> @Box { + let specific = match node.type_id() { + ElementNodeTypeId(HTMLImageElementTypeId) => { + match self.build_box_info_for_image(node) { + None => GenericBox, + Some(image_box_info) => ImageBox(image_box_info), + } + } + TextNodeTypeId => UnscannedTextBox(UnscannedTextBoxInfo::new(&node)), + _ => GenericBox, + }; + @Box::new(node, specific) } /// Creates an inline flow from a set of inline boxes and adds it as a child of the given flow. @@ -245,7 +235,7 @@ impl<'self> FlowConstructor<'self> { /// otherwise. #[inline(always)] fn flush_inline_boxes_to_flow(&self, - boxes: ~[@RenderBox], + boxes: ~[@Box], flow: &mut ~Flow:, node: AbstractNode) { if boxes.len() > 0 { @@ -259,7 +249,7 @@ impl<'self> FlowConstructor<'self> { /// Creates an inline flow from a set of inline boxes, if present, and adds it as a child of /// the given flow. fn flush_inline_boxes_to_flow_if_necessary(&self, - opt_boxes: &mut Option<~[@RenderBox]>, + opt_boxes: &mut Option<~[@Box]>, flow: &mut ~Flow:, node: AbstractNode) { let opt_boxes = util::replace(opt_boxes, None); @@ -378,13 +368,13 @@ impl<'self> FlowConstructor<'self> { let mut opt_inline_block_splits = None; let mut opt_box_accumulator = None; - // Concatenate all the render boxes of our kids, creating {ib} splits as necessary. + // Concatenate all the boxes of our kids, creating {ib} splits as necessary. for kid in node.children() { match kid.swap_out_construction_result() { NoConstructionResult => {} FlowConstructionResult(flow) => { // {ib} split. Flush the accumulator to our new split and make a new - // accumulator to hold any subsequent `RenderBox`es we come across. + // accumulator to hold any subsequent boxes we come across. let split = InlineBlockSplit { predecessor_boxes: util::replace(&mut opt_box_accumulator, None).to_vec(), flow: flow, @@ -438,7 +428,7 @@ impl<'self> FlowConstructor<'self> { } /// Creates an `InlineBoxesConstructionResult` for replaced content. Replaced content doesn't - /// render its children, so this just nukes a child's boxes and creates a `RenderBox`. + /// render its children, so this just nukes a child's boxes and creates a `Box`. fn build_boxes_for_replaced_inline_content(&self, node: AbstractNode) -> ConstructionResult { for kid in node.children() { @@ -454,7 +444,7 @@ impl<'self> FlowConstructor<'self> { ConstructionItemConstructionResult(construction_item) } - /// Builds one or more render boxes for a node with `display: inline`. This yields an + /// Builds one or more boxes for a node with `display: inline`. This yields an /// `InlineBoxesConstructionResult`. fn build_boxes_for_inline(&self, node: AbstractNode) -> ConstructionResult { // Is this node replaced content? @@ -462,8 +452,7 @@ impl<'self> FlowConstructor<'self> { // Go to a path that concatenates our kids' boxes. self.build_boxes_for_nonreplaced_inline_content(node) } else { - // Otherwise, just nuke our kids' boxes, create our `RenderBox` if any, and be done - // with it. + // Otherwise, just nuke our kids' boxes, create our box if any, and be done with it. self.build_boxes_for_replaced_inline_content(node) } } @@ -494,7 +483,7 @@ impl<'self> PostorderNodeTraversal for FlowConstructor<'self> { } } - // Inline items contribute inline render box construction results. + // Inline items contribute inline box construction results. (display::inline, float::none) => { let construction_result = self.build_boxes_for_inline(node); node.set_flow_construction_result(construction_result) @@ -575,7 +564,7 @@ impl NodeUtils for AbstractNode { } /// Strips ignorable whitespace from the start of a list of boxes. -fn strip_ignorable_whitespace_from_start(opt_boxes: &mut Option<~[@RenderBox]>) { +fn strip_ignorable_whitespace_from_start(opt_boxes: &mut Option<~[@Box]>) { match util::replace(opt_boxes, None) { None => return, Some(boxes) => { @@ -597,7 +586,7 @@ fn strip_ignorable_whitespace_from_start(opt_boxes: &mut Option<~[@RenderBox]>) } /// Strips ignorable whitespace from the end of a list of boxes. -fn strip_ignorable_whitespace_from_end(opt_boxes: &mut Option<~[@RenderBox]>) { +fn strip_ignorable_whitespace_from_end(opt_boxes: &mut Option<~[@Box]>) { match *opt_boxes { None => {} Some(ref mut boxes) => { diff --git a/src/components/main/layout/display_list_builder.rs b/src/components/main/layout/display_list_builder.rs index 5e8ebc12305..1a5800c08fb 100644 --- a/src/components/main/layout/display_list_builder.rs +++ b/src/components/main/layout/display_list_builder.rs @@ -2,9 +2,9 @@ * 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/. */ -//! Constructs display lists from render boxes. +//! Constructs display lists from boxes. -use layout::box::{RenderBox, RenderBoxUtils}; +use layout::box::Box; use layout::context::LayoutContext; use std::cast::transmute; use script::dom::node::AbstractNode; @@ -16,27 +16,27 @@ use style; /// that nodes in this view shoud not really be touched. The idea is to /// store the nodes in the display list and have layout transmute them. pub trait ExtraDisplayListData { - fn new(box: &@RenderBox) -> Self; + fn new(box: &@Box) -> Self; } pub type Nothing = (); impl ExtraDisplayListData for AbstractNode<()> { - fn new(box: &@RenderBox) -> AbstractNode<()> { + fn new(box: &@Box) -> AbstractNode<()> { unsafe { - transmute(box.base().node) + transmute(box.node) } } } impl ExtraDisplayListData for Nothing { - fn new(_: &@RenderBox) -> Nothing { + fn new(_: &@Box) -> Nothing { () } } -impl ExtraDisplayListData for @RenderBox { - fn new(box: &@RenderBox) -> @RenderBox { +impl ExtraDisplayListData for @Box { + fn new(box: &@Box) -> @Box { *box } } diff --git a/src/components/main/layout/flow.rs b/src/components/main/layout/flow.rs index ecf6d34e47d..bc8f344c0cd 100644 --- a/src/components/main/layout/flow.rs +++ b/src/components/main/layout/flow.rs @@ -2,23 +2,23 @@ * 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/. */ -//! Servo's experimental layout system builds a tree of `Flow` and `RenderBox` objects and -/// solves layout constraints to obtain positions and display attributes of tree nodes. Positions -/// are computed in several tree traversals driven by the fundamental data dependencies required by +//! Servo's experimental layout system builds a tree of `Flow` and `Box` objects and solves +//! layout constraints to obtain positions and display attributes of tree nodes. Positions are +//! computed in several tree traversals driven by the fundamental data dependencies required by /// inline and block layout. /// /// Flows are interior nodes in the layout tree and correspond closely to *flow contexts* in the -/// CSS specification. Flows are responsible for positioning their child flow contexts and render -/// boxes. Flows have purpose-specific fields, such as auxiliary line box structs, out-of-flow -/// child lists, and so on. +/// CSS specification. Flows are responsible for positioning their child flow contexts and boxes. +/// Flows have purpose-specific fields, such as auxiliary line box structs, out-of-flow child +/// lists, and so on. /// /// Currently, the important types of flows are: /// /// * `BlockFlow`: A flow that establishes a block context. It has several child flows, each of /// which are positioned according to block formatting context rules (CSS block boxes). Block /// flows also contain a single `GenericBox` to represent their rendered borders, padding, etc. -/// (In the future, this render box may be folded into `BlockFlow` to save space.) The BlockFlow -/// at the root of the tree has special behavior: it stretches to the boundaries of the viewport. +/// (In the future, this box may be folded into `BlockFlow` to save space.) The BlockFlow at the +/// root of the tree has special behavior: it stretches to the boundaries of the viewport. /// /// * `InlineFlow`: A flow that establishes an inline context. It has a flat list of child /// boxes/flows that are subject to inline layout and line breaking and structs to represent @@ -27,7 +27,7 @@ use css::node_style::StyledNode; use layout::block::BlockFlow; -use layout::box::RenderBox; +use layout::box::Box; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::float_context::{FloatContext, Invalid}; @@ -340,12 +340,12 @@ pub struct FlowData { } pub struct BoxIterator { - priv boxes: ~[@RenderBox], + priv boxes: ~[@Box], priv index: uint, } -impl Iterator<@RenderBox> for BoxIterator { - fn next(&mut self) -> Option<@RenderBox> { +impl Iterator<@Box> for BoxIterator { + fn next(&mut self) -> Option<@Box> { if self.index >= self.boxes.len() { None } else { diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index a904f15707c..9c43a05e851 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use css::node_style::StyledNode; -use layout::box::{CannotSplit, GenericRenderBoxClass, ImageRenderBoxClass, RenderBox}; -use layout::box::{RenderBoxUtils, SplitDidFit, SplitDidNotFit, TextRenderBoxClass}; +use layout::box::{Box, CannotSplit, GenericBox, ImageBox, ScannedTextBox, SplitDidFit}; +use layout::box::{SplitDidNotFit, UnscannedTextBox}; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::flow::{FlowClass, Flow, FlowData, InlineFlowClass}; @@ -25,34 +25,31 @@ use std::cell::Cell; use std::u16; use std::util; -/* -Lineboxes are represented as offsets into the child list, rather than -as an object that "owns" boxes. Choosing a different set of line -breaks requires a new list of offsets, and possibly some splitting and -merging of TextBoxes. - -A similar list will keep track of the mapping between CSS boxes and -the corresponding render boxes in the inline flow. - -After line breaks are determined, render boxes in the inline flow may -overlap visually. For example, in the case of nested inline CSS boxes, -outer inlines must be at least as large as the inner inlines, for -purposes of drawing noninherited things like backgrounds, borders, -outlines. - -N.B. roc has an alternative design where the list instead consists of -things like "start outer box, text, start inner box, text, end inner -box, text, end outer box, text". This seems a little complicated to -serve as the starting point, but the current design doesn't make it -hard to try out that alternative. - -Line boxes also contain some metadata used during line breaking. The -green zone is the area that the line can expand to before it collides -with a float or a horizontal wall of the containing block. The top -left corner of the green zone is the same as that of the line, but -the green zone can be taller and wider than the line itself. -*/ - +/// Lineboxes are represented as offsets into the child list, rather than +/// as an object that "owns" boxes. Choosing a different set of line +/// breaks requires a new list of offsets, and possibly some splitting and +/// merging of TextBoxes. +/// +/// A similar list will keep track of the mapping between CSS boxes and +/// the corresponding boxes in the inline flow. +/// +/// After line breaks are determined, render boxes in the inline flow may +/// overlap visually. For example, in the case of nested inline CSS boxes, +/// outer inlines must be at least as large as the inner inlines, for +/// purposes of drawing noninherited things like backgrounds, borders, +/// outlines. +/// +/// N.B. roc has an alternative design where the list instead consists of +/// things like "start outer box, text, start inner box, text, end inner +/// box, text, end outer box, text". This seems a little complicated to +/// serve as the starting point, but the current design doesn't make it +/// hard to try out that alternative. +/// +/// Line boxes also contain some metadata used during line breaking. The +/// green zone is the area that the line can expand to before it collides +/// with a float or a horizontal wall of the containing block. The top +/// left corner of the green zone is the same as that of the line, but +/// the green zone can be taller and wider than the line itself. struct LineBox { range: Range, bounds: Rect, @@ -61,8 +58,9 @@ struct LineBox { struct LineboxScanner { floats: FloatContext, - new_boxes: ~[@RenderBox], - work_list: @mut RingBuf<@RenderBox>, + new_boxes: ~[@Box], + /// FIXME(pcwalton): `@mut`? :( + work_list: @mut RingBuf<@Box>, pending_line: LineBox, lines: ~[LineBox], cur_y: Au, @@ -128,11 +126,11 @@ impl LineboxScanner { break } let box = flow.boxes[i]; i += 1; - debug!("LineboxScanner: Working with box from box list: b{:d}", box.base().id()); + debug!("LineboxScanner: Working with box from box list: b{}", box.debug_id()); box } else { let box = self.work_list.pop_front().unwrap(); - debug!("LineboxScanner: Working with box from work list: b{:d}", box.base().id()); + debug!("LineboxScanner: Working with box from work list: b{}", box.debug_id()); box }; @@ -179,7 +177,7 @@ impl LineboxScanner { // FIXME(eatkinson): this assumes that the tallest box in the line determines the line height // This might not be the case with some weird text fonts. - fn new_height_for_line(&self, new_box: &RenderBox) -> Au { + fn new_height_for_line(&self, new_box: &Box) -> Au { let box_height = new_box.box_height(); if box_height > self.pending_line.bounds.size.height { box_height @@ -188,14 +186,14 @@ impl LineboxScanner { } } - /// Computes the position of a line that has only the provided RenderBox. - /// Returns: the bounding rect of the line's green zone (whose origin coincides - /// with the line's origin) and the actual width of the first box after splitting. - fn initial_line_placement(&self, first_box: @RenderBox, ceiling: Au, flow: &mut InlineFlow) + /// Computes the position of a line that has only the provided box. Returns the bounding rect + /// of the line's green zone (whose origin coincides with the line's origin) and the actual + /// width of the first box after splitting. + fn initial_line_placement(&self, first_box: @Box, ceiling: Au, flow: &mut InlineFlow) -> (Rect, Au) { debug!("LineboxScanner: Trying to place first box of line {}", self.lines.len()); - let first_box_size = first_box.base().position.get().size; + let first_box_size = first_box.position.get().size; let splittable = first_box.can_split(); debug!("LineboxScanner: box size: {}, splittable: {}", first_box_size, splittable); let line_is_empty: bool = self.pending_line.range.length() == 0; @@ -250,9 +248,9 @@ impl LineboxScanner { debug!("LineboxScanner: case=box split and fit"); let actual_box_width = match (left, right) { - (Some(l_box), Some(_)) => l_box.base().position.get().size.width, - (Some(l_box), None) => l_box.base().position.get().size.width, - (None, Some(r_box)) => r_box.base().position.get().size.width, + (Some(l_box), Some(_)) => l_box.position.get().size.width, + (Some(l_box), None) => l_box.position.get().size.width, + (None, Some(r_box)) => r_box.position.get().size.width, (None, None) => fail!("This case makes no sense.") }; return (line_bounds, actual_box_width); @@ -264,9 +262,9 @@ impl LineboxScanner { debug!("LineboxScanner: case=box split and fit didn't fit; trying to push it down"); let actual_box_width = match (left, right) { - (Some(l_box), Some(_)) => l_box.base().position.get().size.width, - (Some(l_box), None) => l_box.base().position.get().size.width, - (None, Some(r_box)) => r_box.base().position.get().size.width, + (Some(l_box), Some(_)) => l_box.position.get().size.width, + (Some(l_box), None) => l_box.position.get().size.width, + (None, Some(r_box)) => r_box.position.get().size.width, (None, None) => fail!("This case makes no sense.") }; @@ -281,7 +279,7 @@ impl LineboxScanner { } /// Returns false only if we should break the line. - fn try_append_to_line(&mut self, in_box: @RenderBox, flow: &mut InlineFlow) -> bool { + fn try_append_to_line(&mut self, in_box: @Box, flow: &mut InlineFlow) -> bool { let line_is_empty: bool = self.pending_line.range.length() == 0; if line_is_empty { @@ -293,7 +291,7 @@ impl LineboxScanner { debug!("LineboxScanner: Trying to append box to line {:u} (box size: {}, green zone: \ {}): {:s}", self.lines.len(), - in_box.base().position.get().size, + in_box.position.get().size, self.pending_line.green_zone, in_box.debug_str()); @@ -349,8 +347,7 @@ impl LineboxScanner { // horizontally. We'll try to place the whole box on this line and break somewhere // if it doesn't fit. - let new_width = self.pending_line.bounds.size.width + - in_box.base().position.get().size.width; + let new_width = self.pending_line.bounds.size.width + in_box.position.get().size.width; if new_width <= green_zone.width { debug!("LineboxScanner: case=box fits without splitting"); @@ -424,8 +421,8 @@ impl LineboxScanner { } // unconditional push - fn push_box_to_line(&mut self, box: @RenderBox) { - debug!("LineboxScanner: Pushing box b{:d} to line {:u}", box.base().id(), self.lines.len()); + fn push_box_to_line(&mut self, box: @Box) { + debug!("LineboxScanner: Pushing box {} to line {:u}", box.debug_id(), self.lines.len()); if self.pending_line.range.length() == 0 { assert!(self.new_boxes.len() <= (u16::max_value as uint)); @@ -433,9 +430,9 @@ impl LineboxScanner { } self.pending_line.range.extend_by(1); self.pending_line.bounds.size.width = self.pending_line.bounds.size.width + - box.base().position.get().size.width; + box.position.get().size.width; self.pending_line.bounds.size.height = Au::max(self.pending_line.bounds.size.height, - box.base().position.get().size.height); + box.position.get().size.height); self.new_boxes.push(box); } } @@ -444,9 +441,9 @@ pub struct InlineFlow { /// Data common to all flows. base: FlowData, - // A vec of all inline render boxes. Several boxes may - // correspond to one Node/Element. - boxes: ~[@RenderBox], + /// A vector of all inline render boxes. Several boxes may correspond to one node/element. + boxes: ~[@Box], + // vec of ranges into boxes that represents line positions. // these ranges are disjoint, and are the result of inline layout. // also some metadata used for positioning lines @@ -467,7 +464,7 @@ impl InlineFlow { } } - pub fn from_boxes(base: FlowData, boxes: ~[@RenderBox]) -> InlineFlow { + pub fn from_boxes(base: FlowData, boxes: ~[@Box]) -> InlineFlow { InlineFlow { base: base, boxes: boxes, @@ -560,8 +557,7 @@ impl Flow for InlineFlow { fn assign_widths(&mut self, _: &mut LayoutContext) { // Initialize content box widths if they haven't been initialized already. // - // TODO: Combine this with `LineboxScanner`'s walk in the box list, or put this into - // `RenderBox`. + // TODO: Combine this with `LineboxScanner`'s walk in the box list, or put this into `Box`. debug!("assign_widths_inline: floats_in: {:?}", self.base.floats_in); { @@ -620,14 +616,13 @@ impl Flow for InlineFlow { if slack_width < Au::new(0) { slack_width = Au::new(0); } - //assert!(slack_width >= Au::new(0), "Too many boxes on line"); // Get the text alignment. // TODO(Issue #222): use 'text-align' property from InlineFlow's // block container, not from the style of the first box child. let linebox_align = if line.range.begin() < self.boxes.len() { let first_box = self.boxes[line.range.begin()]; - first_box.base().nearest_ancestor_element().style().Text.text_align + first_box.nearest_ancestor_element().style().Text.text_align } else { // Nothing to lay out, so assume left alignment. text_align::left @@ -640,7 +635,7 @@ impl Flow for InlineFlow { // TODO(Issue #213): implement `text-align: justify` text_align::left | text_align::justify => { for i in line.range.eachi() { - let box = self.boxes[i].base(); + let box = &self.boxes[i]; box.position.mutate().ptr.origin.x = offset_x; offset_x = offset_x + box.position.get().size.width; } @@ -648,7 +643,7 @@ impl Flow for InlineFlow { text_align::center => { offset_x = offset_x + slack_width.scale_by(0.5); for i in line.range.eachi() { - let box = self.boxes[i].base(); + let box = &self.boxes[i]; box.position.mutate().ptr.origin.x = offset_x; offset_x = offset_x + box.position.get().size.width; } @@ -656,7 +651,7 @@ impl Flow for InlineFlow { text_align::right => { offset_x = offset_x + slack_width; for i in line.range.eachi() { - let box = self.boxes[i].base(); + let box = &self.boxes[i]; box.position.mutate().ptr.origin.x = offset_x; offset_x = offset_x + box.position.get().size.width; } @@ -680,41 +675,40 @@ impl Flow for InlineFlow { for box_i in line.range.eachi() { let cur_box = self.boxes[box_i]; - let (top_from_base, bottom_from_base, ascent) = match cur_box.class() { - ImageRenderBoxClass => { - let image_box = cur_box.as_image_render_box(); - let mut height = image_box.image_height(); + // FIXME(pcwalton): Move into `box.rs` like the rest of box-specific layout code? + let (top_from_base, bottom_from_base, ascent) = match cur_box.specific { + ImageBox(ref image_box) => { + let mut height = image_box.image_height(cur_box); // TODO: margin, border, padding's top and bottom should be calculated in advance, // since baseline of image is bottom margin edge. let mut top; let mut bottom; { - let base = &image_box.base; - top = base.border.top + base.padding.top + base.margin.top; - bottom = base.border.bottom + base.padding.bottom + - base.margin.bottom; + top = cur_box.border.get().top + cur_box.padding.get().top + + cur_box.margin.get().top; + bottom = cur_box.border.get().bottom + cur_box.padding.get().bottom + + cur_box.margin.get().bottom; } let noncontent_height = top + bottom; height = height + noncontent_height; - let position_ref = image_box.base.position.mutate(); + let position_ref = cur_box.position.mutate(); position_ref.ptr.size.height = height; position_ref.ptr.translate(&Point2D(Au::new(0), -height)); let ascent = height + bottom; (height, Au::new(0), ascent) }, - TextRenderBoxClass => { - let text_box = cur_box.as_text_render_box(); + ScannedTextBox(ref text_box) => { let range = &text_box.range; let run = &text_box.run; // Compute the height based on the line-height and font size let text_bounds = run.metrics_for_range(range).bounding_box; let em_size = text_bounds.size.height; - let line_height = text_box.base.calculate_line_height(em_size); + let line_height = cur_box.calculate_line_height(em_size); // Find the top and bottom of the content area. // Those are used in text-top and text-bottom value of 'vertical-align' @@ -722,20 +716,16 @@ impl Flow for InlineFlow { // Offset from the top of the box is 1/2 of the leading + ascent let text_offset = text_ascent + (line_height - em_size).scale_by(0.5); - text_bounds.translate(&Point2D(text_box.base.position.get().origin.x, - Au::new(0))); + text_bounds.translate(&Point2D(cur_box.position.get().origin.x, Au(0))); (text_offset, line_height - text_offset, text_ascent) }, - GenericRenderBoxClass => { - let base = cur_box.base(); - let height = base.position.get().size.height; + GenericBox => { + let height = cur_box.position.get().size.height; (height, Au::new(0), height) }, - // FIXME(pcwalton): This isn't very type safe! - _ => { - fail!("Tried to assign height to unknown Box variant: {:s}", - cur_box.debug_str()) + UnscannedTextBox(_) => { + fail!("Unscanned text boxes should have been scanned by now.") } }; let mut top_from_base = top_from_base; @@ -753,9 +743,8 @@ impl Flow for InlineFlow { // It should calculate the distance from baseline to the bottom of parent's content area. // But, it is assumed now as 0. let parent_text_bottom = Au::new(0); - let cur_box_base = cur_box.base(); // Get parent node - let parent = cur_box_base.node.parent_node(); + let parent = cur_box.node.parent_node(); let font_size = parent.unwrap().style().Font.font_size; parent_text_top = font_size; @@ -764,7 +753,7 @@ impl Flow for InlineFlow { // That is, if the box has top or bottom value, no_update_flag becomes true. let mut no_update_flag = false; // Calculate a relative offset from baseline. - let offset = match cur_box_base.vertical_align() { + let offset = match cur_box.vertical_align() { vertical_align::baseline => { -ascent }, @@ -819,9 +808,8 @@ impl Flow for InlineFlow { -(length + ascent) }, vertical_align::Percentage(p) => { - let pt_size = cur_box.base().font_style().pt_size; - let line_height = cur_box.base() - .calculate_line_height(Au::from_pt(pt_size)); + let pt_size = cur_box.font_style().pt_size; + let line_height = cur_box.calculate_line_height(Au::from_pt(pt_size)); let percent_offset = line_height.scale_by(p); -(percent_offset + ascent) } @@ -836,7 +824,7 @@ impl Flow for InlineFlow { bottommost = bottom_from_base; } - cur_box.base().position.mutate().ptr.origin.y = line.bounds.origin.y + offset; + cur_box.position.mutate().ptr.origin.y = line.bounds.origin.y + offset; } // Calculate the distance from baseline to the top of the biggest box with 'bottom' value. @@ -859,15 +847,14 @@ impl Flow for InlineFlow { // All boxes' y position is updated following the new baseline offset. for box_i in line.range.eachi() { let cur_box = self.boxes[box_i]; - let cur_box_base = cur_box.base(); - let adjust_offset = match cur_box_base.vertical_align() { + let adjust_offset = match cur_box.vertical_align() { vertical_align::top => Au::new(0), vertical_align::bottom => baseline_offset + bottommost, _ => baseline_offset, }; - cur_box_base.position.mutate().ptr.origin.y = - cur_box_base.position.get().origin.y + adjust_offset; + cur_box.position.mutate().ptr.origin.y = cur_box.position.get().origin.y + + adjust_offset; } // This is used to set the top y position of the next linebox in the next loop. diff --git a/src/components/main/layout/text.rs b/src/components/main/layout/text.rs index c5f56a10ef9..00459f42b7f 100644 --- a/src/components/main/layout/text.rs +++ b/src/components/main/layout/text.rs @@ -4,13 +4,13 @@ //! Text layout. -use std::vec; +use layout::box::{Box, ScannedTextBox, ScannedTextBoxInfo, UnscannedTextBox}; +use layout::context::LayoutContext; +use layout::flow::Flow; use gfx::text::text_run::TextRun; use gfx::text::util::{CompressWhitespaceNewline, transform_text}; -use layout::box::{RenderBox, RenderBoxUtils, TextRenderBox, UnscannedTextRenderBoxClass}; -use layout::context::LayoutContext; -use layout::flow::Flow; +use std::vec; use servo_util::range::Range; /// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextBox`es. @@ -56,27 +56,20 @@ impl TextRunScanner { flow.as_inline().boxes = out_boxes; // A helper function. - fn can_coalesce_text_nodes(boxes: &[@RenderBox], left_i: uint, right_i: uint) -> bool { + fn can_coalesce_text_nodes(boxes: &[@Box], left_i: uint, right_i: uint) -> bool { assert!(left_i < boxes.len()); assert!(right_i > 0 && right_i < boxes.len()); assert!(left_i != right_i); - - let (left, right) = (boxes[left_i], boxes[right_i]); - match (left.class(), right.class()) { - (UnscannedTextRenderBoxClass, UnscannedTextRenderBoxClass) => { - left.can_merge_with_box(right) - } - (_, _) => false - } + boxes[left_i].can_merge_with_box(boxes[right_i]) } } - /// A "clump" is a range of inline flow leaves that can be merged together into a single - /// `RenderBox`. Adjacent text with the same style can be merged, and nothing else can. + /// A "clump" is a range of inline flow leaves that can be merged together into a single box. + /// Adjacent text with the same style can be merged, and nothing else can. /// - /// The flow keeps track of the `RenderBox`es contained by all non-leaf DOM nodes. This is - /// necessary for correct painting order. Since we compress several leaf `RenderBox`es here, - /// the mapping must be adjusted. + /// The flow keeps track of the boxes contained by all non-leaf DOM nodes. This is necessary + /// for correct painting order. Since we compress several leaf boxes here, the mapping must be + /// adjusted. /// /// N.B. `in_boxes` is passed by reference, since the old code used a `DVec`. The caller is /// responsible for swapping out the list. It is not clear to me (pcwalton) that this is still @@ -85,7 +78,7 @@ impl TextRunScanner { ctx: &LayoutContext, flow: &mut Flow, last_whitespace: bool, - out_boxes: &mut ~[@RenderBox]) + out_boxes: &mut ~[@Box]) -> bool { let inline = flow.as_inline(); let in_boxes = &inline.boxes; @@ -95,10 +88,12 @@ impl TextRunScanner { debug!("TextRunScanner: flushing boxes in range={}", self.clump); let is_singleton = self.clump.length() == 1; let possible_text_clump = in_boxes[self.clump.begin()]; // FIXME(pcwalton): Rust bug - let is_text_clump = possible_text_clump.class() == UnscannedTextRenderBoxClass; + let is_text_clump = match possible_text_clump.specific { + UnscannedTextBox(_) => true, + _ => false, + }; let mut new_whitespace = last_whitespace; - match (is_singleton, is_text_clump) { (false, false) => { fail!(~"WAT: can't coalesce non-text nodes in flush_clump_to_list()!") @@ -109,14 +104,20 @@ impl TextRunScanner { }, (true, true) => { let old_box = in_boxes[self.clump.begin()]; - let text = old_box.as_unscanned_text_render_box().raw_text(); - let font_style = old_box.base().font_style(); - let decoration = old_box.base().text_decoration(); + let text = match old_box.specific { + UnscannedTextBox(ref text_box_info) => &text_box_info.text, + _ => fail!("Expected an unscanned text box!"), + }; + + let font_style = old_box.font_style(); + let decoration = old_box.text_decoration(); // TODO(#115): Use the actual CSS `white-space` property of the relevant style. let compression = CompressWhitespaceNewline; - let (transformed_text, whitespace) = transform_text(text, compression, last_whitespace); + let (transformed_text, whitespace) = transform_text(*text, + compression, + last_whitespace); new_whitespace = whitespace; if transformed_text.len() > 0 { @@ -126,11 +127,15 @@ impl TextRunScanner { let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&font_style); let run = @fontgroup.create_textrun(transformed_text, decoration); - debug!("TextRunScanner: pushing single text box in range: {} ({})", self.clump, text); + debug!("TextRunScanner: pushing single text box in range: {} ({})", + self.clump, + *text); let range = Range::new(0, run.char_len()); - let new_box = @TextRenderBox::new((*old_box.base()).clone(), run, range); - - out_boxes.push(new_box as @RenderBox); + let new_text_box_info = ScannedTextBoxInfo::new(run, range); + let new_metrics = run.metrics_for_range(&range); + let new_box = @old_box.transform(new_metrics.bounding_box.size, + ScannedTextBox(new_text_box_info)); + out_boxes.push(new_box) } }, (false, true) => { @@ -144,8 +149,12 @@ impl TextRunScanner { // `transform_text`, so that boxes starting and/or ending with whitespace can // be compressed correctly with respect to the text run. let idx = i + self.clump.begin(); - let in_box = in_boxes[idx].as_unscanned_text_render_box().raw_text(); - let (new_str, new_whitespace) = transform_text(in_box, + let in_box = match in_boxes[idx].specific { + UnscannedTextBox(ref text_box_info) => &text_box_info.text, + _ => fail!("Expected an unscanned text box!"), + }; + + let (new_str, new_whitespace) = transform_text(*in_box, compression, last_whitespace_in_clump); last_whitespace_in_clump = new_whitespace; @@ -171,9 +180,9 @@ impl TextRunScanner { // font group fonts. This is probably achieved by creating the font group above // and then letting `FontGroup` decide which `Font` to stick into the text run. let in_box = in_boxes[self.clump.begin()]; - let font_style = in_box.base().font_style(); + let font_style = in_box.font_style(); let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&font_style); - let decoration = in_box.base().text_decoration(); + let decoration = in_box.text_decoration(); // TextRuns contain a cycle which is usually resolved by the teardown // sequence. If no clump takes ownership, however, it will leak. @@ -195,10 +204,11 @@ impl TextRunScanner { continue } - let new_box = @TextRenderBox::new((*in_boxes[i].base()).clone(), - run.unwrap(), - range); - out_boxes.push(new_box as @RenderBox); + let new_text_box_info = ScannedTextBoxInfo::new(run.unwrap(), range); + let new_metrics = new_text_box_info.run.metrics_for_range(&range); + let new_box = @in_boxes[i].transform(new_metrics.bounding_box.size, + ScannedTextBox(new_text_box_info)); + out_boxes.push(new_box) } } } // End of match. diff --git a/src/components/main/layout/util.rs b/src/components/main/layout/util.rs index 306f613f715..73132433f6c 100644 --- a/src/components/main/layout/util.rs +++ b/src/components/main/layout/util.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use layout::box::{RenderBox, RenderBoxUtils}; +use layout::box::Box; use layout::construct::{ConstructionResult, NoConstructionResult}; use extra::arc::Arc; @@ -76,7 +76,7 @@ impl ElementMapping { self.entries.iter().enumerate() } - pub fn repair_for_box_changes(&mut self, old_boxes: &[@RenderBox], new_boxes: &[@RenderBox]) { + pub fn repair_for_box_changes(&mut self, old_boxes: &[@Box], new_boxes: &[@Box]) { let entries = &mut self.entries; debug!("--- Old boxes: ---"); @@ -118,8 +118,7 @@ impl ElementMapping { repair_stack.push(item); entries_k += 1; } - while new_j < new_boxes.len() && - old_boxes[old_i].base().node != new_boxes[new_j].base().node { + while new_j < new_boxes.len() && old_boxes[old_i].node != new_boxes[new_j].node { debug!("repair_for_box_changes: Slide through new box {:u}", new_j); new_j += 1; }