layout: Largely move display list building out to a separate file.

`layout::fragment` and `layout::block` were getting too big.
This commit is contained in:
Patrick Walton 2014-10-21 21:05:44 -07:00
parent 691e42f7ef
commit 821793351e
13 changed files with 843 additions and 785 deletions

View file

@ -19,44 +19,33 @@ use layout_debug;
use model::{Auto, IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, Specified, specified};
use model;
use text;
use util::{OpaqueNodeMethods, ToGfxColor};
use util::OpaqueNodeMethods;
use wrapper::{TLayoutNode, ThreadSafeLayoutNode};
use geom::{Point2D, Rect, Size2D, SideOffsets2D};
use geom::approxeq::ApproxEq;
use gfx::color::rgb;
use gfx::display_list::{BackgroundAndBorderLevel, BaseDisplayItem, BorderDisplayItem};
use gfx::display_list::{BorderDisplayItemClass, ContentStackingLevel, DisplayList};
use gfx::display_list::{ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem};
use gfx::display_list::{LineDisplayItemClass, OpaqueNode, PseudoDisplayItemClass};
use gfx::display_list::{SidewaysLeft, SidewaysRight, SolidColorDisplayItem};
use gfx::display_list::{SolidColorDisplayItemClass, StackingLevel, TextDisplayItem};
use gfx::display_list::{TextDisplayItemClass, Upright};
use geom::Size2D;
use gfx::display_list::OpaqueNode;
use gfx::text::glyph::CharIndex;
use gfx::text::text_run::TextRun;
use script_traits::UntrustedNodeAddress;
use serialize::{Encodable, Encoder};
use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg, PipelineId, SubpageId};
use servo_msg::constellation_msg::{PipelineId, SubpageId};
use servo_net::image::holder::ImageHolder;
use servo_net::local_image_cache::LocalImageCache;
use servo_util::geometry::{Au, ZERO_RECT};
use servo_util::geometry::Au;
use servo_util::geometry;
use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin, WritingMode};
use servo_util::opts;
use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin};
use servo_util::range::*;
use servo_util::smallvec::SmallVec;
use servo_util::str::is_whitespace;
use std::cmp::{max, min};
use std::fmt;
use std::from_str::FromStr;
use std::num::Zero;
use string_cache::Atom;
use style::{ComputedValues, TElement, TNode, cascade_anonymous, RGBA};
use style::{ComputedValues, TElement, TNode, cascade_anonymous};
use style::computed_values::{LengthOrPercentage, LengthOrPercentageOrAuto};
use style::computed_values::{LengthOrPercentageOrNone};
use style::computed_values::{overflow, LPA_Auto, background_attachment};
use style::computed_values::{background_repeat, border_style, clear, position, text_align};
use style::computed_values::{text_decoration, vertical_align, visibility, white_space};
use style::computed_values::{LPA_Auto, clear, position, text_align, text_decoration};
use style::computed_values::{vertical_align, white_space};
use sync::{Arc, Mutex};
use url::Url;
@ -132,6 +121,8 @@ impl<E, S: Encoder<E>> Encodable<S, E> for Fragment {
}
/// Info specific to the kind of fragment. Keep this enum small.
///
/// FIXME(pcwalton): We have completely failed at keeping this enum small.
#[deriving(Clone)]
pub enum SpecificFragmentInfo {
GenericFragment,
@ -935,480 +926,6 @@ impl Fragment {
}
}
/// Adds the display items necessary to paint the background of this fragment to the display
/// list if necessary.
pub fn build_display_list_for_background_if_applicable(&self,
style: &ComputedValues,
list: &mut DisplayList,
layout_context: &LayoutContext,
level: StackingLevel,
absolute_bounds: &Rect<Au>,
clip_rect: &Rect<Au>) {
// FIXME: This causes a lot of background colors to be displayed when they are clearly not
// needed. We could use display list optimization to clean this up, but it still seems
// inefficient. What we really want is something like "nearest ancestor element that
// doesn't have a fragment".
let background_color = style.resolve_color(style.get_background().background_color);
if !background_color.alpha.approx_eq(&0.0) {
let display_item = box SolidColorDisplayItem {
base: BaseDisplayItem::new(*absolute_bounds, self.node, level, *clip_rect),
color: background_color.to_gfx_color(),
};
list.push(SolidColorDisplayItemClass(display_item))
}
// The background image is painted on top of the background color.
// Implements background image, per spec:
// http://www.w3.org/TR/CSS21/colors.html#background
let background = style.get_background();
let image_url = match background.background_image {
None => return,
Some(ref image_url) => image_url,
};
let mut holder = ImageHolder::new(image_url.clone(), layout_context.shared.image_cache.clone());
let image = match holder.get_image(self.node.to_untrusted_node_address()) {
None => {
// No image data at all? Do nothing.
//
// TODO: Add some kind of placeholder background image.
debug!("(building display list) no background image :(");
return
}
Some(image) => image,
};
debug!("(building display list) building background image");
let image_width = Au::from_px(image.width as int);
let image_height = Au::from_px(image.height as int);
let mut bounds = *absolute_bounds;
// Clip.
//
// TODO: Check the bounds to see if a clip item is actually required.
let clip_rect = clip_rect.intersection(&bounds).unwrap_or(ZERO_RECT);
// Use background-attachment to get the initial virtual origin
let (virtual_origin_x, virtual_origin_y) = match background.background_attachment {
background_attachment::scroll => {
(absolute_bounds.origin.x, absolute_bounds.origin.y)
}
background_attachment::fixed => {
(Au(0), Au(0))
}
};
// Use background-position to get the offset
let horizontal_position = model::specified(background.background_position.horizontal,
bounds.size.width - image_width);
let vertical_position = model::specified(background.background_position.vertical,
bounds.size.height - image_height);
let abs_x = virtual_origin_x + horizontal_position;
let abs_y = virtual_origin_y + vertical_position;
// Adjust origin and size based on background-repeat
match background.background_repeat {
background_repeat::no_repeat => {
bounds.origin.x = abs_x;
bounds.origin.y = abs_y;
bounds.size.width = image_width;
bounds.size.height = image_height;
}
background_repeat::repeat_x => {
bounds.origin.y = abs_y;
bounds.size.height = image_height;
ImageFragmentInfo::tile_image(&mut bounds.origin.x, &mut bounds.size.width,
abs_x, image.width);
}
background_repeat::repeat_y => {
bounds.origin.x = abs_x;
bounds.size.width = image_width;
ImageFragmentInfo::tile_image(&mut bounds.origin.y, &mut bounds.size.height,
abs_y, image.height);
}
background_repeat::repeat => {
ImageFragmentInfo::tile_image(&mut bounds.origin.x, &mut bounds.size.width,
abs_x, image.width);
ImageFragmentInfo::tile_image(&mut bounds.origin.y, &mut bounds.size.height,
abs_y, image.height);
}
};
// Create the image display item.
let image_display_item = ImageDisplayItemClass(box ImageDisplayItem {
base: BaseDisplayItem::new(bounds, self.node, level, clip_rect),
image: image.clone(),
stretch_size: Size2D(Au::from_px(image.width as int),
Au::from_px(image.height as int)),
});
list.push(image_display_item)
}
/// Adds the display items necessary to paint the borders of this fragment to a display list if
/// necessary.
pub fn build_display_list_for_borders_if_applicable(&self,
style: &ComputedValues,
list: &mut DisplayList,
abs_bounds: &Rect<Au>,
level: StackingLevel,
clip_rect: &Rect<Au>) {
let border = style.logical_border_width();
if border.is_zero() {
return
}
let top_color = style.resolve_color(style.get_border().border_top_color);
let right_color = style.resolve_color(style.get_border().border_right_color);
let bottom_color = style.resolve_color(style.get_border().border_bottom_color);
let left_color = style.resolve_color(style.get_border().border_left_color);
// Append the border to the display list.
let border_display_item = box BorderDisplayItem {
base: BaseDisplayItem::new(*abs_bounds, self.node, level, *clip_rect),
border: border.to_physical(style.writing_mode),
color: SideOffsets2D::new(top_color.to_gfx_color(),
right_color.to_gfx_color(),
bottom_color.to_gfx_color(),
left_color.to_gfx_color()),
style: SideOffsets2D::new(style.get_border().border_top_style,
style.get_border().border_right_style,
style.get_border().border_bottom_style,
style.get_border().border_left_style)
};
list.push(BorderDisplayItemClass(border_display_item))
}
fn build_debug_borders_around_text_fragments(&self,
display_list: &mut DisplayList,
flow_origin: Point2D<Au>,
text_fragment: &ScannedTextFragmentInfo,
clip_rect: &Rect<Au>) {
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
// Fragment position wrt to the owning flow.
let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
let absolute_fragment_bounds = Rect(
fragment_bounds.origin + flow_origin,
fragment_bounds.size);
// Compute the text fragment bounds and draw a border surrounding them.
let border_display_item = box BorderDisplayItem {
base: BaseDisplayItem::new(absolute_fragment_bounds,
self.node,
ContentStackingLevel,
*clip_rect),
border: SideOffsets2D::new_all_same(Au::from_px(1)),
color: SideOffsets2D::new_all_same(rgb(0, 0, 200)),
style: SideOffsets2D::new_all_same(border_style::solid)
};
display_list.push(BorderDisplayItemClass(border_display_item));
// Draw a rectangle representing the baselines.
let ascent = text_fragment.run.ascent();
let mut baseline = self.border_box.clone();
baseline.start.b = baseline.start.b + ascent;
baseline.size.block = Au(0);
let mut baseline = baseline.to_physical(self.style.writing_mode, container_size);
baseline.origin = baseline.origin + flow_origin;
let line_display_item = box LineDisplayItem {
base: BaseDisplayItem::new(baseline, self.node, ContentStackingLevel, *clip_rect),
color: rgb(0, 200, 0),
style: border_style::dashed,
};
display_list.push(LineDisplayItemClass(line_display_item));
}
fn build_debug_borders_around_fragment(&self,
display_list: &mut DisplayList,
flow_origin: Point2D<Au>,
clip_rect: &Rect<Au>) {
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
// Fragment position wrt to the owning flow.
let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
let absolute_fragment_bounds = Rect(
fragment_bounds.origin + flow_origin,
fragment_bounds.size);
// This prints a debug border around the border of this fragment.
let border_display_item = box BorderDisplayItem {
base: BaseDisplayItem::new(absolute_fragment_bounds,
self.node,
ContentStackingLevel,
*clip_rect),
border: SideOffsets2D::new_all_same(Au::from_px(1)),
color: SideOffsets2D::new_all_same(rgb(0, 0, 200)),
style: SideOffsets2D::new_all_same(border_style::solid)
};
display_list.push(BorderDisplayItemClass(border_display_item))
}
/// Adds the display items for this fragment to the given stacking context.
///
/// Arguments:
///
/// * `display_list`: The unflattened display list to add display items to.
/// * `layout_context`: The layout context.
/// * `dirty`: The dirty rectangle in the coordinate system of the owning flow.
/// * `flow_origin`: Position of the origin of the owning flow wrt the display list root flow.
/// * `clip_rect`: The rectangle to clip the display items to.
pub fn build_display_list(&mut self,
display_list: &mut DisplayList,
layout_context: &LayoutContext,
flow_origin: Point2D<Au>,
background_and_border_level: BackgroundAndBorderLevel,
clip_rect: &Rect<Au>) {
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
let rect_to_absolute = |writing_mode: WritingMode, logical_rect: LogicalRect<Au>| {
let physical_rect = logical_rect.to_physical(writing_mode, container_size);
Rect(physical_rect.origin + flow_origin, physical_rect.size)
};
// Fragment position wrt to the owning flow.
let absolute_fragment_bounds = rect_to_absolute(self.style.writing_mode, self.border_box);
debug!("Fragment::build_display_list at rel={}, abs={}: {}",
self.border_box,
absolute_fragment_bounds,
self);
debug!("Fragment::build_display_list: dirty={}, flow_origin={}",
layout_context.shared.dirty,
flow_origin);
if self.style().get_inheritedbox().visibility != visibility::visible {
return
}
if !absolute_fragment_bounds.intersects(&layout_context.shared.dirty) {
debug!("Fragment::build_display_list: Did not intersect...");
return
}
debug!("Fragment::build_display_list: intersected. Adding display item...");
if self.is_primary_fragment() {
let level =
StackingLevel::from_background_and_border_level(background_and_border_level);
// Add a pseudo-display item for content box queries. This is a very bogus thing to do.
let base_display_item = box BaseDisplayItem::new(absolute_fragment_bounds,
self.node,
level,
*clip_rect);
display_list.push(PseudoDisplayItemClass(base_display_item));
// Add the background to the list, if applicable.
match self.inline_context {
Some(ref inline_context) => {
for style in inline_context.styles.iter().rev() {
self.build_display_list_for_background_if_applicable(
&**style,
display_list,
layout_context,
level,
&absolute_fragment_bounds,
clip_rect);
}
}
None => {}
}
match self.specific {
ScannedTextFragment(_) => {},
_ => {
self.build_display_list_for_background_if_applicable(
&*self.style,
display_list,
layout_context,
level,
&absolute_fragment_bounds,
clip_rect);
}
}
// Add a border, if applicable.
//
// TODO: Outlines.
match self.inline_context {
Some(ref inline_context) => {
for style in inline_context.styles.iter().rev() {
self.build_display_list_for_borders_if_applicable(
&**style,
display_list,
&absolute_fragment_bounds,
level,
clip_rect);
}
}
None => {}
}
match self.specific {
ScannedTextFragment(_) => {},
_ => {
self.build_display_list_for_borders_if_applicable(
&*self.style,
display_list,
&absolute_fragment_bounds,
level,
clip_rect);
}
}
}
let content_box = self.content_box();
let absolute_content_box = rect_to_absolute(self.style.writing_mode, content_box);
// Create special per-fragment-type display items.
match self.specific {
UnscannedTextFragment(_) => fail!("Shouldn't see unscanned fragments here."),
TableColumnFragment(_) => fail!("Shouldn't see table column fragments here."),
ScannedTextFragment(ref text_fragment) => {
// Create the text display item.
let orientation = if self.style.writing_mode.is_vertical() {
if self.style.writing_mode.is_sideways_left() {
SidewaysLeft
} else {
SidewaysRight
}
} else {
Upright
};
let metrics = &text_fragment.run.font_metrics;
let baseline_origin ={
let mut tmp = content_box.start;
tmp.b = tmp.b + metrics.ascent;
tmp.to_physical(self.style.writing_mode, container_size) + flow_origin
};
let text_display_item = box TextDisplayItem {
base: BaseDisplayItem::new(absolute_content_box,
self.node,
ContentStackingLevel,
*clip_rect),
text_run: text_fragment.run.clone(),
range: text_fragment.range,
text_color: self.style().get_color().color.to_gfx_color(),
orientation: orientation,
baseline_origin: baseline_origin,
};
display_list.push(TextDisplayItemClass(text_display_item));
// Create display items for text decoration
{
let line = |maybe_color: Option<RGBA>, rect: || -> LogicalRect<Au>| {
match maybe_color {
None => {},
Some(color) => {
display_list.push(SolidColorDisplayItemClass(
box SolidColorDisplayItem {
base: BaseDisplayItem::new(
rect_to_absolute(
self.style.writing_mode,
rect()),
self.node,
ContentStackingLevel,
*clip_rect),
color: color.to_gfx_color(),
}));
}
}
};
let text_decorations =
self.style().get_inheritedtext()._servo_text_decorations_in_effect;
line(text_decorations.underline, || {
let mut rect = content_box.clone();
rect.start.b = rect.start.b + metrics.ascent - metrics.underline_offset;
rect.size.block = metrics.underline_size;
rect
});
line(text_decorations.overline, || {
let mut rect = content_box.clone();
rect.size.block = metrics.underline_size;
rect
});
line(text_decorations.line_through, || {
let mut rect = content_box.clone();
rect.start.b = rect.start.b + metrics.ascent - metrics.strikeout_offset;
rect.size.block = metrics.strikeout_size;
rect
});
}
if opts::get().show_debug_fragment_borders {
self.build_debug_borders_around_text_fragments(display_list,
flow_origin,
text_fragment,
clip_rect);
}
}
GenericFragment | IframeFragment(..) | TableFragment | TableCellFragment |
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) | InputFragment |
InlineAbsoluteHypotheticalFragment(_) => {
if opts::get().show_debug_fragment_borders {
self.build_debug_borders_around_fragment(display_list,
flow_origin,
clip_rect);
}
}
ImageFragment(ref mut image_fragment) => {
let image_ref = &mut image_fragment.image;
match image_ref.get_image(self.node.to_untrusted_node_address()) {
Some(image) => {
debug!("(building display list) building image fragment");
// Place the image into the display list.
let image_display_item = box ImageDisplayItem {
base: BaseDisplayItem::new(absolute_content_box,
self.node,
ContentStackingLevel,
*clip_rect),
image: image.clone(),
stretch_size: absolute_content_box.size,
};
display_list.push(ImageDisplayItemClass(image_display_item))
}
None => {
// No image data at all? Do nothing.
//
// TODO: Add some kind of placeholder image.
debug!("(building display list) no image :(");
}
}
}
}
// FIXME(pcwalton): This is a bit of an abuse of the logging
// infrastructure. We should have a real `SERVO_DEBUG` system.
debug!("{:?}",
self.build_debug_borders_around_fragment(display_list, flow_origin, clip_rect))
// If this is an iframe, then send its position and size up to the constellation.
//
// FIXME(pcwalton): Doing this during display list construction seems potentially
// problematic if iframes are outside the area we're computing the display list for, since
// they won't be able to reflow at all until the user scrolls to them. Perhaps we should
// separate this into two parts: first we should send the size only to the constellation
// once that's computed during assign-block-sizes, and second we should should send the
// origin to the constellation here during display list construction. This should work
// because layout for the iframe only needs to know size, and origin is only relevant if
// the iframe is actually going to be displayed.
match self.specific {
IframeFragment(ref iframe_fragment) => {
self.finalize_position_and_size_of_iframe(iframe_fragment,
absolute_fragment_bounds.origin,
layout_context)
}
_ => {}
}
}
/// Computes the intrinsic inline-sizes of this fragment.
pub fn compute_intrinsic_inline_sizes(&mut self) -> IntrinsicISizesContribution {
let mut result = self.style_specified_intrinsic_inline_size();
@ -1889,29 +1406,6 @@ impl Fragment {
}
}
/// Sends the size and position of this iframe fragment to the constellation. This is out of
/// line to guide inlining.
#[inline(never)]
fn finalize_position_and_size_of_iframe(&self,
iframe_fragment: &IframeFragmentInfo,
offset: Point2D<Au>,
layout_context: &LayoutContext) {
let border_padding = (self.border_padding).to_physical(self.style.writing_mode);
let content_size = self.content_box().size.to_physical(self.style.writing_mode);
let iframe_rect = Rect(Point2D(geometry::to_frac_px(offset.x + border_padding.left) as f32,
geometry::to_frac_px(offset.y + border_padding.top) as f32),
Size2D(geometry::to_frac_px(content_size.width) as f32,
geometry::to_frac_px(content_size.height) as f32));
debug!("finalizing position and size of iframe for {:?},{:?}",
iframe_fragment.pipeline_id,
iframe_fragment.subpage_id);
let ConstellationChan(ref chan) = layout_context.shared.constellation_chan;
chan.send(FrameRectMsg(iframe_fragment.pipeline_id,
iframe_fragment.subpage_id,
iframe_rect));
}
/// Returns true if and only if this is the *primary fragment* for the fragment's style object
/// (conceptually, though style sharing makes this not really true, of course). The primary
/// fragment is the one that draws backgrounds, borders, etc., and takes borders, padding and
@ -1921,7 +1415,7 @@ impl Fragment {
/// fragments. Inline-block fragments are not primary fragments because the corresponding block
/// flow is the primary fragment, while table wrapper fragments are not primary fragments
/// because the corresponding table flow is the primary fragment.
fn is_primary_fragment(&self) -> bool {
pub fn is_primary_fragment(&self) -> bool {
match self.specific {
InlineBlockFragment(_) | InlineAbsoluteHypotheticalFragment(_) |
TableWrapperFragment => false,
@ -1951,27 +1445,6 @@ impl Fragment {
}
}
pub fn clip_rect_for_children(&self, current_clip_rect: Rect<Au>, flow_origin: Point2D<Au>)
-> Rect<Au> {
// Don't clip if we're text.
match self.specific {
ScannedTextFragment(_) => return current_clip_rect,
_ => {}
}
// Only clip if `overflow` tells us to.
match self.style.get_box().overflow {
overflow::hidden | overflow::auto | overflow::scroll => {}
_ => return current_clip_rect,
}
// Create a new clip rect.
//
// FIXME(#2795): Get the real container size.
let physical_rect = self.border_box.to_physical(self.style.writing_mode, Size2D::zero());
current_clip_rect.intersection(&Rect(physical_rect.origin + flow_origin,
physical_rect.size)).unwrap_or(ZERO_RECT)
}
}
impl fmt::Show for Fragment {