/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! The `Fragment` type, which represents the leaves of the layout tree. #![deny(unsafe_block)] use css::node_style::StyledNode; use construct::FlowConstructor; use context::LayoutContext; use floats::{ClearBoth, ClearLeft, ClearRight, ClearType}; use flow::Flow; use flow; use inline::{InlineFragmentContext, InlineMetrics}; use model::{Auto, IntrinsicISizes, MaybeAuto, Specified, specified}; use model; use text; use util::{OpaqueNodeMethods, ToGfxColor}; 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, ClipDisplayItem, ClipDisplayItemClass}; use gfx::display_list::{ContentStackingLevel, DisplayItem, DisplayList, ImageDisplayItem}; use gfx::display_list::{ImageDisplayItemClass, LineDisplayItem}; use gfx::display_list::{LineDisplayItemClass, OpaqueNode, PseudoDisplayItemClass}; use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, StackingLevel}; use gfx::display_list::{TextDisplayItem, TextDisplayItemClass}; use gfx::display_list::{Upright, SidewaysLeft, SidewaysRight}; use gfx::font::FontStyle; use gfx::text::glyph::CharIndex; use gfx::text::text_run::TextRun; use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg, PipelineId, SubpageId}; use servo_net::image::holder::ImageHolder; use servo_net::local_image_cache::LocalImageCache; use servo_util::geometry::Au; use servo_util::geometry; use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin}; use servo_util::range::*; use servo_util::namespace; use servo_util::smallvec::SmallVec; use servo_util::str::is_whitespace; use std::fmt; use std::from_str::FromStr; use std::mem; use std::num::Zero; use style::{ComputedValues, TElement, TNode, cascade_anonymous, RGBA}; use style::computed_values::{LengthOrPercentageOrAuto, 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 sync::{Arc, Mutex}; use url::Url; /// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position themselves. In /// general, fragments do not have a simple correspondence with CSS fragments in the specification: /// /// * Several fragments may correspond to the same CSS box or DOM node. For example, a CSS text box /// broken across two lines is represented by two fragments. /// /// * Some CSS fragments are not created at all, such as some anonymous block fragments induced by inline /// fragments with block-level sibling fragments. In that case, Servo uses an `InlineFlow` with /// `BlockFlow` siblings; the `InlineFlow` is block-level, but not a block container. It is /// positioned as if it were a block fragment, but its children are positioned according to inline /// flow. /// /// A `GenericFragment` is an empty fragment that contributes only borders, margins, padding, and /// backgrounds. It is analogous to a CSS nonreplaced content box. /// /// A fragment'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 fragments may also contain custom data; for example, text fragments contain text. /// /// FIXME(#2260, pcwalton): This can be slimmed down some. #[deriving(Clone)] pub struct Fragment { /// An opaque reference to the DOM node that this `Fragment` originates from. pub node: OpaqueNode, /// The CSS style of this fragment. pub style: Arc, /// The position of this fragment relative to its owning flow. /// The size includes padding and border, but not margin. pub border_box: LogicalRect, /// The sum of border and padding; i.e. the distance from the edge of the border box to the /// content edge of the fragment. pub border_padding: LogicalMargin, /// The margin of the content box. pub margin: LogicalMargin, /// Info specific to the kind of fragment. Keep this enum small. pub specific: SpecificFragmentInfo, /// New-line chracter(\n)'s positions(relative, not absolute) /// /// FIXME(#2260, pcwalton): This is very inefficient; remove. pub new_line_pos: Vec, /// Holds the style context information for fragments /// that are part of an inline formatting context. pub inline_context: Option, } /// Info specific to the kind of fragment. Keep this enum small. #[deriving(Clone)] pub enum SpecificFragmentInfo { GenericFragment, ImageFragment(ImageFragmentInfo), IframeFragment(IframeFragmentInfo), ScannedTextFragment(ScannedTextFragmentInfo), TableFragment, TableCellFragment, TableColumnFragment(TableColumnFragmentInfo), TableRowFragment, TableWrapperFragment, UnscannedTextFragment(UnscannedTextFragmentInfo), } /// A fragment that represents a replaced content image and its accompanying borders, shadows, etc. #[deriving(Clone)] pub struct ImageFragmentInfo { /// The image held within this fragment. pub image: ImageHolder, pub computed_inline_size: Option, pub computed_block_size: Option, pub dom_inline_size: Option, pub dom_block_size: Option, pub writing_mode_is_vertical: bool, } impl ImageFragmentInfo { /// Creates a new image fragment from the given URL and local image cache. /// /// FIXME(pcwalton): The fact that image fragments store the cache in the fragment makes little sense to /// me. pub fn new(node: &ThreadSafeLayoutNode, image_url: Url, local_image_cache: Arc>) -> ImageFragmentInfo { fn convert_length(node: &ThreadSafeLayoutNode, name: &str) -> Option { let element = node.as_element(); element.get_attr(&namespace::Null, name).and_then(|string| { let n: Option = FromStr::from_str(string); n }).and_then(|pixels| Some(Au::from_px(pixels))) } let is_vertical = node.style().writing_mode.is_vertical(); let dom_width = convert_length(node, "width"); let dom_height = convert_length(node, "height"); ImageFragmentInfo { image: ImageHolder::new(image_url, local_image_cache), computed_inline_size: None, computed_block_size: None, dom_inline_size: if is_vertical { dom_height } else { dom_width }, dom_block_size: if is_vertical { dom_width } else { dom_height }, writing_mode_is_vertical: is_vertical, } } /// Returns the calculated inline-size of the image, accounting for the inline-size attribute. pub fn computed_inline_size(&self) -> Au { self.computed_inline_size.expect("image inline_size is not computed yet!") } /// Returns the calculated block-size of the image, accounting for the block-size attribute. pub fn computed_block_size(&self) -> Au { self.computed_block_size.expect("image block_size is not computed yet!") } /// Returns the original inline-size of the image. pub fn image_inline_size(&mut self) -> Au { let size = self.image.get_size().unwrap_or(Size2D::zero()); Au::from_px(if self.writing_mode_is_vertical { size.height } else { size.width }) } /// Returns the original block-size of the image. pub fn image_block_size(&mut self) -> Au { let size = self.image.get_size().unwrap_or(Size2D::zero()); Au::from_px(if self.writing_mode_is_vertical { size.width } else { size.height }) } // Return used value for inline-size or block-size. // // `dom_length`: inline-size or block-size as specified in the `img` tag. // `style_length`: inline-size as given in the CSS pub fn style_length(style_length: LengthOrPercentageOrAuto, dom_length: Option, container_inline_size: Au) -> MaybeAuto { match (MaybeAuto::from_style(style_length,container_inline_size),dom_length) { (Specified(length),_) => { Specified(length) }, (Auto,Some(length)) => { Specified(length) }, (Auto,None) => { Auto } } } } /// A fragment that represents an inline frame (iframe). This stores the pipeline ID so that the size /// of this iframe can be communicated via the constellation to the iframe's own layout task. #[deriving(Clone)] pub struct IframeFragmentInfo { /// The pipeline ID of this iframe. pub pipeline_id: PipelineId, /// The subpage ID of this iframe. pub subpage_id: SubpageId, } impl IframeFragmentInfo { /// Creates the information specific to an iframe fragment. pub fn new(node: &ThreadSafeLayoutNode) -> IframeFragmentInfo { let (pipeline_id, subpage_id) = node.iframe_pipeline_and_subpage_ids(); IframeFragmentInfo { pipeline_id: pipeline_id, subpage_id: subpage_id, } } } /// A scanned text fragment represents a single run of text with a distinct style. A `TextFragment` /// may be split into two or more fragments across line breaks. Several `TextFragment`s may /// correspond to a single DOM text node. Split text fragments are implemented by referring to /// subsets of a single `TextRun` object. #[deriving(Clone)] pub struct ScannedTextFragmentInfo { /// The text run that this represents. pub run: Arc>, /// The range within the above text run that this represents. pub range: Range, } impl ScannedTextFragmentInfo { /// Creates the information specific to a scanned text fragment from a range and a text run. pub fn new(run: Arc>, range: Range) -> ScannedTextFragmentInfo { ScannedTextFragmentInfo { run: run, range: range, } } } #[deriving(Show)] pub struct SplitInfo { // TODO(bjz): this should only need to be a single character index, but both values are // currently needed for splitting in the `inline::try_append_*` functions. pub range: Range, pub inline_size: Au, } impl SplitInfo { fn new(range: Range, info: &ScannedTextFragmentInfo) -> SplitInfo { SplitInfo { range: range, inline_size: info.run.advance_for_range(&range), } } } /// Data for an unscanned text fragment. Unscanned text fragments are the results of flow construction that /// have not yet had their inline-size determined. #[deriving(Clone)] pub struct UnscannedTextFragmentInfo { /// The text inside the fragment. pub text: String, } impl UnscannedTextFragmentInfo { /// Creates a new instance of `UnscannedTextFragmentInfo` from the given DOM node. pub fn new(node: &ThreadSafeLayoutNode) -> UnscannedTextFragmentInfo { // FIXME(pcwalton): Don't copy text; atomically reference count it instead. UnscannedTextFragmentInfo { text: node.text(), } } /// Creates a new instance of `UnscannedTextFragmentInfo` from the given text. #[inline] pub fn from_text(text: String) -> UnscannedTextFragmentInfo { UnscannedTextFragmentInfo { text: text, } } } /// A fragment that represents a table column. #[deriving(Clone)] pub struct TableColumnFragmentInfo { /// the number of columns a element should span pub span: Option, } impl TableColumnFragmentInfo { /// Create the information specific to an table column fragment. pub fn new(node: &ThreadSafeLayoutNode) -> TableColumnFragmentInfo { let span = { let element = node.as_element(); element.get_attr(&namespace::Null, "span").and_then(|string| { let n: Option = FromStr::from_str(string); n }) }; TableColumnFragmentInfo { span: span, } } } impl Fragment { /// Constructs a new `Fragment` instance for the given node. /// /// Arguments: /// /// * `constructor`: The flow constructor. /// /// * `node`: The node to create a fragment for. pub fn new(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> Fragment { let style = node.style().clone(); let writing_mode = style.writing_mode; Fragment { node: OpaqueNodeMethods::from_thread_safe_layout_node(node), style: style, border_box: LogicalRect::zero(writing_mode), border_padding: LogicalMargin::zero(writing_mode), margin: LogicalMargin::zero(writing_mode), specific: constructor.build_specific_fragment_info_for_node(node), new_line_pos: vec!(), inline_context: None, } } /// Constructs a new `Fragment` instance from a specific info. pub fn new_from_specific_info(node: &ThreadSafeLayoutNode, specific: SpecificFragmentInfo) -> Fragment { let style = node.style().clone(); let writing_mode = style.writing_mode; Fragment { node: OpaqueNodeMethods::from_thread_safe_layout_node(node), style: style, border_box: LogicalRect::zero(writing_mode), border_padding: LogicalMargin::zero(writing_mode), margin: LogicalMargin::zero(writing_mode), specific: specific, new_line_pos: vec!(), inline_context: None, } } /// Constructs a new `Fragment` instance for an anonymous table object. pub fn new_anonymous_table_fragment(node: &ThreadSafeLayoutNode, specific: SpecificFragmentInfo) -> Fragment { // CSS 2.1 § 17.2.1 This is for non-inherited properties on anonymous table fragments // example: // //
// Foo //
// // Anonymous table fragments, TableRowFragment and TableCellFragment, are generated around `Foo`, but it shouldn't inherit the border. let node_style = cascade_anonymous(&**node.style()); let writing_mode = node_style.writing_mode; Fragment { node: OpaqueNodeMethods::from_thread_safe_layout_node(node), style: Arc::new(node_style), border_box: LogicalRect::zero(writing_mode), border_padding: LogicalMargin::zero(writing_mode), margin: LogicalMargin::zero(writing_mode), specific: specific, new_line_pos: vec!(), inline_context: None, } } /// Constructs a new `Fragment` instance from an opaque node. pub fn from_opaque_node_and_style(node: OpaqueNode, style: Arc, specific: SpecificFragmentInfo) -> Fragment { let writing_mode = style.writing_mode; Fragment { node: node, style: style, border_box: LogicalRect::zero(writing_mode), border_padding: LogicalMargin::zero(writing_mode), margin: LogicalMargin::zero(writing_mode), specific: specific, new_line_pos: vec!(), inline_context: None, } } /// Returns a debug ID of this fragment. This ID should not be considered stable across multiple /// layouts or fragment manipulations. pub fn debug_id(&self) -> uint { self as *const Fragment as uint } /// Transforms this fragment into another fragment of the given type, with the given size, preserving all /// the other data. pub fn transform(&self, size: LogicalSize, specific: SpecificFragmentInfo) -> Fragment { Fragment { node: self.node, style: self.style.clone(), border_box: LogicalRect::from_point_size( self.style.writing_mode, self.border_box.start, size), border_padding: self.border_padding, margin: self.margin, specific: specific, new_line_pos: self.new_line_pos.clone(), inline_context: self.inline_context.clone(), } } /// Adds a style to the inline context for this fragment. If the inline /// context doesn't exist yet, it will be created. pub fn add_inline_context_style(&mut self, style: Arc) { if self.inline_context.is_none() { self.inline_context = Some(InlineFragmentContext::new()); } self.inline_context.get_mut_ref().styles.push(style.clone()); } /// Uses the style only to estimate the intrinsic inline-sizes. These may be modified for text or /// replaced elements. fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizes { let (use_margins, use_padding) = match self.specific { GenericFragment | IframeFragment(_) | ImageFragment(_) => (true, true), TableFragment | TableCellFragment => (false, true), TableWrapperFragment => (true, false), TableRowFragment => (false, false), ScannedTextFragment(_) | TableColumnFragment(_) | UnscannedTextFragment(_) => { // Styles are irrelevant for these kinds of fragments. return IntrinsicISizes::new() } }; let style = self.style(); let inline_size = MaybeAuto::from_style(style.content_inline_size(), Au::new(0)).specified_or_zero(); let margin = style.logical_margin(); let (margin_inline_start, margin_inline_end) = if use_margins { (MaybeAuto::from_style(margin.inline_start, Au(0)).specified_or_zero(), MaybeAuto::from_style(margin.inline_end, Au(0)).specified_or_zero()) } else { (Au(0), Au(0)) }; let padding = style.logical_padding(); let (padding_inline_start, padding_inline_end) = if use_padding { (model::specified(padding.inline_start, Au(0)), model::specified(padding.inline_end, Au(0))) } else { (Au(0), Au(0)) }; // FIXME(#2261, pcwalton): This won't work well for inlines: is this OK? let border = self.border_width(); let surround_inline_size = margin_inline_start + margin_inline_end + padding_inline_start + padding_inline_end + border.inline_start_end(); IntrinsicISizes { minimum_inline_size: inline_size, preferred_inline_size: inline_size, surround_inline_size: surround_inline_size, } } pub fn calculate_line_height(&self, layout_context: &LayoutContext) -> Au { let font_style = text::computed_style_to_font_style(&*self.style); let font_metrics = text::font_metrics_for_style(layout_context.font_context(), &font_style); text::line_height_from_style(&*self.style, &font_metrics) } /// Returns the sum of the inline-sizes of all the borders of this fragment. This is private because /// it should only be called during intrinsic inline-size computation or computation of /// `border_padding`. Other consumers of this information should simply consult that field. #[inline] fn border_width(&self) -> LogicalMargin { match self.inline_context { None => self.style().logical_border_width(), Some(ref inline_fragment_context) => { let zero = LogicalMargin::zero(self.style.writing_mode); inline_fragment_context.styles.iter().fold(zero, |acc, style| acc + style.logical_border_width()) } } } /// Computes the border, padding, and vertical margins from the containing block inline-size and the /// style. After this call, the `border_padding` and the vertical direction of the `margin` /// field will be correct. pub fn compute_border_padding_margins(&mut self, containing_block_inline_size: Au) { // Compute vertical margins. Note that this value will be ignored by layout if the style // specifies `auto`. match self.specific { TableFragment | TableCellFragment | TableRowFragment | TableColumnFragment(_) => { self.margin.block_start = Au(0); self.margin.block_end = Au(0) } _ => { // NB: Percentages are relative to containing block inline-size (not block-size) per CSS 2.1. let margin = self.style().logical_margin(); self.margin.block_start = MaybeAuto::from_style(margin.block_start, containing_block_inline_size) .specified_or_zero(); self.margin.block_end = MaybeAuto::from_style(margin.block_end, containing_block_inline_size) .specified_or_zero() } } // Compute border. let border = self.border_width(); // Compute padding. let padding = match self.specific { TableColumnFragment(_) | TableRowFragment | TableWrapperFragment => LogicalMargin::zero(self.style.writing_mode), _ => { match self.inline_context { None => model::padding_from_style(self.style(), containing_block_inline_size), Some(ref inline_fragment_context) => { let zero = LogicalMargin::zero(self.style.writing_mode); inline_fragment_context.styles.iter() .fold(zero, |acc, style| acc + model::padding_from_style(&**style, Au(0))) } } } }; self.border_padding = border + padding } // Return offset from original position because of `position: relative`. pub fn relative_position(&self, containing_block_size: &LogicalSize) -> LogicalSize { fn from_style(style: &ComputedValues, container_size: &LogicalSize) -> LogicalSize { let offsets = style.logical_position(); let offset_i = if offsets.inline_start != LPA_Auto { MaybeAuto::from_style(offsets.inline_start, container_size.inline).specified_or_zero() } else { -MaybeAuto::from_style(offsets.inline_end, container_size.inline).specified_or_zero() }; let offset_b = if offsets.block_start != LPA_Auto { MaybeAuto::from_style(offsets.block_start, container_size.inline).specified_or_zero() } else { -MaybeAuto::from_style(offsets.block_end, container_size.inline).specified_or_zero() }; LogicalSize::new(style.writing_mode, offset_i, offset_b) } // Go over the ancestor fragments and add all relative offsets (if any). let mut rel_pos = LogicalSize::zero(self.style.writing_mode); match self.inline_context { None => { if self.style().get_box().position == position::relative { rel_pos = rel_pos + from_style(self.style(), containing_block_size); } } Some(ref inline_fragment_context) => { for style in inline_fragment_context.styles.iter() { if style.get_box().position == position::relative { rel_pos = rel_pos + from_style(&**style, containing_block_size); } } }, } rel_pos } /// 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.style(); match style.get_box().clear { clear::none => None, clear::left => Some(ClearLeft), clear::right => Some(ClearRight), clear::both => Some(ClearBoth), } } /// Converts this fragment's computed style to a font style used for rendering. pub fn font_style(&self) -> FontStyle { text::computed_style_to_font_style(self.style()) } #[inline(always)] pub fn style<'a>(&'a self) -> &'a ComputedValues { &*self.style } /// Returns the text alignment of the computed style of the nearest ancestor-or-self `Element` /// node. pub fn text_align(&self) -> text_align::T { self.style().get_inheritedtext().text_align } pub fn vertical_align(&self) -> vertical_align::T { self.style().get_box().vertical_align } pub fn white_space(&self) -> white_space::T { self.style().get_inheritedtext().white_space } /// Returns the text decoration of this fragment, according to the style of the nearest ancestor /// element. /// /// NB: This may not be the actual text decoration, because of the override rules specified in /// CSS 2.1 § 16.3.1. Unfortunately, computing this properly doesn't really fit into Servo's /// model. Therefore, this is a best lower bound approximation, but the end result may actually /// have the various decoration flags turned on afterward. pub fn text_decoration(&self) -> text_decoration::T { self.style().get_text().text_decoration } /// Returns the inline-start offset from margin edge to content edge. /// /// FIXME(#2262, pcwalton): I think this method is pretty bogus, because it won't work for /// inlines. pub fn inline_start_offset(&self) -> Au { match self.specific { TableWrapperFragment => self.margin.inline_start, TableFragment | TableCellFragment | TableRowFragment => self.border_padding.inline_start, TableColumnFragment(_) => Au(0), _ => self.margin.inline_start + self.border_padding.inline_start, } } /// Returns true if this element can be split. This is true for text fragments. pub fn can_split(&self) -> bool { match self.specific { ScannedTextFragment(..) => true, _ => false, } } /// 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) { // 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), 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() { 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"); // Adjust bounds for `background-position` and `background-attachment`. let mut bounds = *absolute_bounds; let horizontal_position = model::specified(background.background_position.horizontal, bounds.size.width); let vertical_position = model::specified(background.background_position.vertical, bounds.size.height); let clip_display_item; match background.background_attachment { background_attachment::scroll => { clip_display_item = None; bounds.origin.x = bounds.origin.x + horizontal_position; bounds.origin.y = bounds.origin.y + vertical_position; bounds.size.width = bounds.size.width - horizontal_position; bounds.size.height = bounds.size.height - vertical_position; } background_attachment::fixed => { clip_display_item = Some(box ClipDisplayItem { base: BaseDisplayItem::new(bounds, self.node, level), children: DisplayList::new(), }); bounds = Rect { origin: Point2D(horizontal_position, vertical_position), size: Size2D(bounds.origin.x + bounds.size.width, bounds.origin.y + bounds.size.height), } } } // Adjust sizes for `background-repeat`. match background.background_repeat { background_repeat::no_repeat => { bounds.size.width = Au::from_px(image.width as int); bounds.size.height = Au::from_px(image.height as int) } background_repeat::repeat_x => { bounds.size.height = Au::from_px(image.height as int) } background_repeat::repeat_y => { bounds.size.width = Au::from_px(image.width as int) } background_repeat::repeat => {} }; // Create the image display item. let image_display_item = ImageDisplayItemClass(box ImageDisplayItem { base: BaseDisplayItem::new(bounds, self.node, level), image: image.clone(), stretch_size: Size2D(Au::from_px(image.width as int), Au::from_px(image.height as int)), }); match clip_display_item { None => list.push(image_display_item), Some(mut clip_display_item) => { clip_display_item.children.push(image_display_item); list.push(ClipDisplayItemClass(clip_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, level: StackingLevel) { 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), 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, text_fragment: &ScannedTextFragmentInfo) { // 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), 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), 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) { // 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), 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. pub fn build_display_list(&self, display_list: &mut DisplayList, layout_context: &LayoutContext, flow_origin: Point2D, background_and_border_level: BackgroundAndBorderLevel) -> ChildDisplayListAccumulator { // FIXME(#2795): Get the real container size let container_size = Size2D::zero(); let rect_to_absolute = |logical_rect: LogicalRect| { let physical_rect = logical_rect.to_physical(self.style.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.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); let mut accumulator = ChildDisplayListAccumulator::new(self.style(), absolute_fragment_bounds, self.node, ContentStackingLevel); if self.style().get_inheritedbox().visibility != visibility::visible { return accumulator } if !absolute_fragment_bounds.intersects(&layout_context.shared.dirty) { debug!("Fragment::build_display_list: Did not intersect..."); return accumulator } debug!("Fragment::build_display_list: intersected. Adding display item..."); { 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); 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); } } None => { self.build_display_list_for_background_if_applicable(&*self.style, display_list, layout_context, level, &absolute_fragment_bounds); } } // 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); } } None => { self.build_display_list_for_borders_if_applicable(&*self.style, display_list, &absolute_fragment_bounds, level); } } } let content_box = self.content_box(); let absolute_content_box = rect_to_absolute(content_box); // Add a clip, if applicable. 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), 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, }; accumulator.push(display_list, TextDisplayItemClass(text_display_item)); // Create display items for text decoration { let line = |maybe_color: Option, rect: || -> LogicalRect| { match maybe_color { None => {}, Some(color) => { accumulator.push(display_list, SolidColorDisplayItemClass( box SolidColorDisplayItem { base: BaseDisplayItem::new( rect_to_absolute(rect()), self.node, ContentStackingLevel), 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 }); } // Draw debug frames for text bounds. // // FIXME(#2263, 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_text_fragments(display_list, flow_origin, text_fragment)) }, GenericFragment | IframeFragment(..) | TableFragment | TableCellFragment | TableRowFragment | TableWrapperFragment => { // 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)) }, ImageFragment(_) => { match self.specific { ImageFragment(ref image_fragment) => { let image_ref = &image_fragment.image; match image_ref.get_image_if_present() { 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), image: image.clone(), stretch_size: absolute_content_box.size, }; accumulator.push(display_list, 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 :("); } } } _ => fail!("shouldn't get here"), } // 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)) } } // 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, flow_origin, layout_context) } _ => {} } accumulator } /// Returns the intrinsic inline-sizes of this fragment. pub fn intrinsic_inline_sizes(&mut self) -> IntrinsicISizes { let mut result = self.style_specified_intrinsic_inline_size(); match self.specific { GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableColumnFragment(_) | TableRowFragment | TableWrapperFragment => {} ImageFragment(ref mut image_fragment_info) => { let image_inline_size = image_fragment_info.image_inline_size(); result.minimum_inline_size = geometry::max(result.minimum_inline_size, image_inline_size); result.preferred_inline_size = geometry::max(result.preferred_inline_size, image_inline_size); } ScannedTextFragment(ref text_fragment_info) => { let range = &text_fragment_info.range; let min_line_inline_size = text_fragment_info.run.min_width_for_range(range); // See http://dev.w3.org/csswg/css-sizing/#max-content-inline-size. // TODO: Account for soft wrap opportunities. let max_line_inline_size = text_fragment_info.run.metrics_for_range(range).advance_width; result.minimum_inline_size = geometry::max(result.minimum_inline_size, min_line_inline_size); result.preferred_inline_size = geometry::max(result.preferred_inline_size, max_line_inline_size); } UnscannedTextFragment(..) => fail!("Unscanned text fragments should have been scanned by now!"), } // Take borders and padding for parent inline fragments into account, if necessary. match self.inline_context { None => {} Some(ref context) => { for style in context.styles.iter() { let border_width = style.logical_border_width().inline_start_end(); let padding_inline_size = model::padding_from_style(&**style, Au(0)).inline_start_end(); result.minimum_inline_size = result.minimum_inline_size + border_width + padding_inline_size; result.preferred_inline_size = result.preferred_inline_size + border_width + padding_inline_size; } } } result } /// TODO: What exactly does this function return? Why is it Au(0) for GenericFragment? pub fn content_inline_size(&self) -> Au { match self.specific { GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment | TableWrapperFragment => Au(0), ImageFragment(ref image_fragment_info) => { image_fragment_info.computed_inline_size() } ScannedTextFragment(ref text_fragment_info) => { let (range, run) = (&text_fragment_info.range, &text_fragment_info.run); let text_bounds = run.metrics_for_range(range).bounding_box; text_bounds.size.width } TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"), UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), } } /// Returns, and computes, the block-size of this fragment. pub fn content_block_size(&self, layout_context: &LayoutContext) -> Au { match self.specific { GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment | TableWrapperFragment => Au(0), ImageFragment(ref image_fragment_info) => { image_fragment_info.computed_block_size() } ScannedTextFragment(_) => { // Compute the block-size based on the line-block-size and font size. self.calculate_line_height(layout_context) } TableColumnFragment(_) => fail!("Table column fragments do not have block_size"), UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), } } /// Returns the dimensions of the content box. /// /// This is marked `#[inline]` because it is frequently called when only one or two of the /// values are needed and that will save computation. #[inline] pub fn content_box(&self) -> LogicalRect { self.border_box - self.border_padding } /// Find the split of a fragment that includes a new-line character. /// /// A return value of `None` indicates that the fragment is not splittable. /// Otherwise the split information is returned. The right information is /// optional due to the possibility of it being whitespace. // // TODO(bjz): The text run should be removed in the future, but it is currently needed for // the current method of fragment splitting in the `inline::try_append_*` functions. pub fn find_split_info_by_new_line(&self) -> Option<(SplitInfo, Option, Arc> /* TODO(bjz): remove */)> { match self.specific { GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment | TableRowFragment | TableWrapperFragment => None, TableColumnFragment(_) => fail!("Table column fragments do not need to split"), UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), ScannedTextFragment(ref text_fragment_info) => { let mut new_line_pos = self.new_line_pos.clone(); let cur_new_line_pos = new_line_pos.remove(0).unwrap(); let inline_start_range = Range::new(text_fragment_info.range.begin(), cur_new_line_pos); let inline_end_range = Range::new(text_fragment_info.range.begin() + cur_new_line_pos + CharIndex(1), text_fragment_info.range.length() - (cur_new_line_pos + CharIndex(1))); // Left fragment is for inline-start text of first founded new-line character. let inline_start_fragment = SplitInfo::new(inline_start_range, text_fragment_info); // Right fragment is for inline-end text of first founded new-line character. let inline_end_fragment = if inline_end_range.length() > CharIndex(0) { Some(SplitInfo::new(inline_end_range, text_fragment_info)) } else { None }; Some((inline_start_fragment, inline_end_fragment, text_fragment_info.run.clone())) } } } /// Attempts to find the split positions of a text fragment so that its inline-size is /// no more than `max_inline-size`. /// /// A return value of `None` indicates that the fragment could not be split. /// Otherwise the information pertaining to the split is returned. The inline-start /// and inline-end split information are both optional due to the possibility of /// them being whitespace. // // TODO(bjz): The text run should be removed in the future, but it is currently needed for // the current method of fragment splitting in the `inline::try_append_*` functions. pub fn find_split_info_for_inline_size(&self, start: CharIndex, max_inline_size: Au, starts_line: bool) -> Option<(Option, Option, Arc> /* TODO(bjz): remove */)> { match self.specific { GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment | TableRowFragment | TableWrapperFragment => None, TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"), UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), ScannedTextFragment(ref text_fragment_info) => { let mut pieces_processed_count: uint = 0; let mut remaining_inline_size: Au = max_inline_size; let mut inline_start_range = Range::new(text_fragment_info.range.begin() + start, CharIndex(0)); let mut inline_end_range: Option> = None; debug!("split_to_inline_size: splitting text fragment (strlen={}, range={}, avail_inline_size={})", text_fragment_info.run.text.len(), text_fragment_info.range, max_inline_size); for (glyphs, offset, slice_range) in text_fragment_info.run.iter_slices_for_range( &text_fragment_info.range) { debug!("split_to_inline_size: considering slice (offset={}, range={}, \ remain_inline_size={})", offset, slice_range, remaining_inline_size); let metrics = text_fragment_info.run.metrics_for_slice(glyphs, &slice_range); let advance = metrics.advance_width; let should_continue; if advance <= remaining_inline_size { should_continue = true; if starts_line && pieces_processed_count == 0 && glyphs.is_whitespace() { debug!("split_to_inline_size: case=skipping leading trimmable whitespace"); inline_start_range.shift_by(slice_range.length()); } else { debug!("split_to_inline_size: case=enlarging span"); remaining_inline_size = remaining_inline_size - advance; inline_start_range.extend_by(slice_range.length()); } } else { // The advance is more than the remaining inline-size. 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 // inline-end chunk. if slice_end < text_fragment_info.range.end() { debug!("split_to_inline_size: case=skipping trimmable trailing \ whitespace, then split remainder"); let inline_end_range_end = text_fragment_info.range.end() - slice_end; inline_end_range = Some(Range::new(slice_end, inline_end_range_end)); } else { debug!("split_to_inline_size: case=skipping trimmable trailing \ whitespace"); } } else if slice_begin < text_fragment_info.range.end() { // There are still some things inline-start over at the end of the line. Create // the inline-end chunk. let inline_end_range_end = text_fragment_info.range.end() - slice_begin; inline_end_range = Some(Range::new(slice_begin, inline_end_range_end)); debug!("split_to_inline_size: case=splitting remainder with inline_end range={:?}", inline_end_range); } } pieces_processed_count += 1; if !should_continue { break } } let inline_start_is_some = inline_start_range.length() > CharIndex(0); if (pieces_processed_count == 1 || !inline_start_is_some) && !starts_line { None } else { let inline_start = if inline_start_is_some { Some(SplitInfo::new(inline_start_range, text_fragment_info)) } else { None }; let inline_end = inline_end_range.map(|inline_end_range| SplitInfo::new(inline_end_range, text_fragment_info)); Some((inline_start, inline_end, text_fragment_info.run.clone())) } } } } /// Returns true if this fragment is an unscanned text fragment that consists entirely of whitespace. pub fn is_whitespace_only(&self) -> bool { match self.specific { UnscannedTextFragment(ref text_fragment_info) => is_whitespace(text_fragment_info.text.as_slice()), _ => false, } } /// Assigns replaced inline-size, padding, and margins for this fragment only if it is replaced /// content per CSS 2.1 § 10.3.2. pub fn assign_replaced_inline_size_if_necessary(&mut self, container_inline_size: Au) { match self.specific { GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment | TableWrapperFragment => return, TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"), UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), ImageFragment(_) | ScannedTextFragment(_) => {} }; self.compute_border_padding_margins(container_inline_size); let style_inline_size = self.style().content_inline_size(); let style_block_size = self.style().content_block_size(); let noncontent_inline_size = self.border_padding.inline_start_end(); match self.specific { ScannedTextFragment(_) => { // Scanned text fragments will have already had their content inline-sizes assigned by this // point. self.border_box.size.inline = self.border_box.size.inline + noncontent_inline_size } ImageFragment(ref mut image_fragment_info) => { // TODO(ksh8281): compute border,margin let inline_size = ImageFragmentInfo::style_length(style_inline_size, image_fragment_info.dom_inline_size, container_inline_size); let block_size = ImageFragmentInfo::style_length(style_block_size, image_fragment_info.dom_block_size, Au(0)); let inline_size = match (inline_size,block_size) { (Auto, Auto) => image_fragment_info.image_inline_size(), (Auto,Specified(h)) => { let scale = image_fragment_info. image_block_size().to_f32().unwrap() / h.to_f32().unwrap(); Au::new((image_fragment_info.image_inline_size().to_f32().unwrap() / scale) as i32) }, (Specified(w), _) => w, }; self.border_box.size.inline = inline_size + noncontent_inline_size; image_fragment_info.computed_inline_size = Some(inline_size); } _ => fail!("this case should have been handled above"), } } /// Assign block-size for this fragment if it is replaced content. The inline-size must have been assigned /// first. /// /// Ideally, this should follow CSS 2.1 § 10.6.2. pub fn assign_replaced_block_size_if_necessary(&mut self) { match self.specific { GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment | TableWrapperFragment => return, TableColumnFragment(_) => fail!("Table column fragments do not have block_size"), UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), ImageFragment(_) | ScannedTextFragment(_) => {} } let style_inline_size = self.style().content_inline_size(); let style_block_size = self.style().content_block_size(); let noncontent_block_size = self.border_padding.block_start_end(); match self.specific { ImageFragment(ref mut image_fragment_info) => { // TODO(ksh8281): compute border,margin,padding let inline_size = image_fragment_info.computed_inline_size(); // FIXME(ksh8281): we shouldn't assign block-size this way // we don't know about size of parent's block-size let block_size = ImageFragmentInfo::style_length(style_block_size, image_fragment_info.dom_block_size, Au(0)); let block_size = match (style_inline_size, image_fragment_info.dom_inline_size, block_size) { (LPA_Auto, None, Auto) => { image_fragment_info.image_block_size() }, (_,_,Auto) => { let scale = image_fragment_info.image_inline_size().to_f32().unwrap() / inline_size.to_f32().unwrap(); Au::new((image_fragment_info.image_block_size().to_f32().unwrap() / scale) as i32) }, (_,_,Specified(h)) => { h } }; image_fragment_info.computed_block_size = Some(block_size); self.border_box.size.block = block_size + noncontent_block_size } ScannedTextFragment(_) => { // Scanned text fragments' content block-sizes are calculated by the text run scanner // during flow construction. self.border_box.size.block = self.border_box.size.block + noncontent_block_size } _ => fail!("should have been handled above"), } } /// Calculates block-size above baseline, depth below baseline, and ascent for this fragment when /// used in an inline formatting context. See CSS 2.1 § 10.8.1. pub fn inline_metrics(&self, layout_context: &LayoutContext) -> InlineMetrics { match self.specific { ImageFragment(ref image_fragment_info) => { let computed_block_size = image_fragment_info.computed_block_size(); InlineMetrics { block_size_above_baseline: computed_block_size + self.border_padding.block_start_end(), depth_below_baseline: Au(0), ascent: computed_block_size + self.border_padding.block_end, } } ScannedTextFragment(ref text_fragment) => { // See CSS 2.1 § 10.8.1. let line_height = self.calculate_line_height(layout_context); InlineMetrics::from_font_metrics(&text_fragment.run.font_metrics, line_height) } _ => { InlineMetrics { block_size_above_baseline: self.border_box.size.block, depth_below_baseline: Au(0), ascent: self.border_box.size.block, } } } } /// Returns true if this fragment can merge with another adjacent fragment or false otherwise. pub fn can_merge_with_fragment(&self, other: &Fragment) -> bool { match (&self.specific, &other.specific) { (&UnscannedTextFragment(_), &UnscannedTextFragment(_)) => { // FIXME: Should probably use a whitelist of styles that can safely differ (#3165) self.font_style() == other.font_style() && self.text_decoration() == other.text_decoration() && self.white_space() == other.white_space() } _ => false, } } /// 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, layout_context: &LayoutContext) { let mbp = (self.margin + self.border_padding).to_physical(self.style.writing_mode); let content_size = self.content_box().size.to_physical(self.style.writing_mode); let left = offset.x + mbp.left; let top = offset.y + mbp.top; let width = content_size.width; let height = content_size.height; let origin = Point2D(geometry::to_frac_px(left) as f32, geometry::to_frac_px(top) as f32); let size = Size2D(geometry::to_frac_px(width) as f32, geometry::to_frac_px(height) as f32); let rect = Rect(origin, size); debug!("finalizing position and size of iframe for {:?},{:?}", iframe_fragment.pipeline_id, iframe_fragment.subpage_id); let msg = FrameRectMsg(iframe_fragment.pipeline_id, iframe_fragment.subpage_id, rect); let ConstellationChan(ref chan) = layout_context.shared.constellation_chan; chan.send(msg) } } impl fmt::Show for Fragment { /// Outputs a debugging string describing this fragment. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { try!(write!(f, "({} ", match self.specific { GenericFragment => "GenericFragment", IframeFragment(_) => "IframeFragment", ImageFragment(_) => "ImageFragment", ScannedTextFragment(_) => "ScannedTextFragment", TableFragment => "TableFragment", TableCellFragment => "TableCellFragment", TableColumnFragment(_) => "TableColumnFragment", TableRowFragment => "TableRowFragment", TableWrapperFragment => "TableWrapperFragment", UnscannedTextFragment(_) => "UnscannedTextFragment", })); try!(write!(f, "bp {}", self.border_padding)); try!(write!(f, " ")); try!(write!(f, "m {}", self.margin)); write!(f, ")") } } /// An object that accumulates display lists of child flows, applying a clipping rect if necessary. pub struct ChildDisplayListAccumulator { clip_display_item: Option>, } impl ChildDisplayListAccumulator { /// Creates a `ChildDisplayListAccumulator` from the `overflow` property in the given style. fn new(style: &ComputedValues, bounds: Rect, node: OpaqueNode, level: StackingLevel) -> ChildDisplayListAccumulator { ChildDisplayListAccumulator { clip_display_item: match style.get_box().overflow { overflow::hidden | overflow::auto | overflow::scroll => { Some(box ClipDisplayItem { base: BaseDisplayItem::new(bounds, node, level), children: DisplayList::new(), }) }, overflow::visible => None, } } } /// Pushes the given display item onto this display list. pub fn push(&mut self, parent_display_list: &mut DisplayList, item: DisplayItem) { match self.clip_display_item { None => parent_display_list.push(item), Some(ref mut clip_display_item) => clip_display_item.children.push(item), } } /// Pushes the display items from the given child onto this display list. pub fn push_child(&mut self, parent_display_list: &mut DisplayList, child: &mut Flow) { let kid_display_list = mem::replace(&mut flow::mut_base(child).display_list, DisplayList::new()); match self.clip_display_item { None => parent_display_list.push_all_move(kid_display_list), Some(ref mut clip_display_item) => { clip_display_item.children.push_all_move(kid_display_list) } } } /// Consumes this accumulator and pushes the clipping item, if any, onto the display list /// associated with the given flow, along with the items in the given display list. pub fn finish(self, parent: &mut Flow, mut display_list: DisplayList) { let ChildDisplayListAccumulator { clip_display_item } = self; match clip_display_item { None => {} Some(clip_display_item) => display_list.push(ClipDisplayItemClass(clip_display_item)), } flow::mut_base(parent).display_list = display_list } }