diff options
Diffstat (limited to 'components/layout/fragment.rs')
-rw-r--r-- | components/layout/fragment.rs | 1597 |
1 files changed, 1597 insertions, 0 deletions
diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs new file mode 100644 index 00000000000..191283603b9 --- /dev/null +++ b/components/layout/fragment.rs @@ -0,0 +1,1597 @@ +/* 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 layout_debug; +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 serialize::{Encodable, Encoder}; +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<ComputedValues>, + + /// The position of this fragment relative to its owning flow. + /// The size includes padding and border, but not margin. + pub border_box: LogicalRect<Au>, + + /// 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<Au>, + + /// The margin of the content box. + pub margin: LogicalMargin<Au>, + + /// 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<CharIndex>, + + /// Holds the style context information for fragments + /// that are part of an inline formatting context. + pub inline_context: Option<InlineFragmentContext>, + + /// A debug ID that is consistent for the life of + /// this fragment (via transform etc). + pub debug_id: uint, +} + +impl<E, S: Encoder<E>> Encodable<S, E> for Fragment { + fn encode(&self, e: &mut S) -> Result<(), E> { + e.emit_struct("fragment", 0, |e| { + try!(e.emit_struct_field("id", 0, |e| self.debug_id().encode(e))) + try!(e.emit_struct_field("border_box", 1, |e| self.border_box.encode(e))) + e.emit_struct_field("margin", 2, |e| self.margin.encode(e)) + }) + } +} + +/// 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<Au>, + pub computed_block_size: Option<Au>, + pub dom_inline_size: Option<Au>, + pub dom_block_size: Option<Au>, + 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<Mutex<LocalImageCache>>) + -> ImageFragmentInfo { + fn convert_length(node: &ThreadSafeLayoutNode, name: &str) -> Option<Au> { + let element = node.as_element(); + element.get_attr(&namespace::Null, name).and_then(|string| { + let n: Option<int> = 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<Au>, + 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<Box<TextRun>>, + + /// The range within the above text run that this represents. + pub range: Range<CharIndex>, +} + +impl ScannedTextFragmentInfo { + /// Creates the information specific to a scanned text fragment from a range and a text run. + pub fn new(run: Arc<Box<TextRun>>, range: Range<CharIndex>) -> 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<CharIndex>, + pub inline_size: Au, +} + +impl SplitInfo { + fn new(range: Range<CharIndex>, 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 <col> element should span + pub span: Option<int>, +} + +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<int> = 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, + debug_id: layout_debug::generate_unique_debug_id(), + } + } + + /// 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, + debug_id: layout_debug::generate_unique_debug_id(), + } + } + + /// 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: + // + // <div style="display: table"> + // Foo + // </div> + // + // 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, + debug_id: layout_debug::generate_unique_debug_id(), + } + } + + /// Constructs a new `Fragment` instance from an opaque node. + pub fn from_opaque_node_and_style(node: OpaqueNode, + style: Arc<ComputedValues>, + 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, + debug_id: layout_debug::generate_unique_debug_id(), + } + } + + /// 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.debug_id + } + + /// 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<Au>, 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(), + debug_id: self.debug_id, + } + } + + /// 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<ComputedValues>) { + 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<Au> { + 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<Au>) + -> LogicalSize<Au> { + fn from_style(style: &ComputedValues, container_size: &LogicalSize<Au>) + -> LogicalSize<Au> { + 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<ClearType> { + 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<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), + 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<Au>, + 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<Au>, + 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<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), + 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<Au>, + 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<Au>| { + 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<RGBA>, rect: || -> LogicalRect<Au>| { + 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<Au> { + 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<SplitInfo>, Arc<Box<TextRun>> /* 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<SplitInfo>, Option<SplitInfo>, Arc<Box<TextRun>> /* 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<Range<CharIndex>> = 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<Au>, + 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<Box<ClipDisplayItem>>, +} + +impl ChildDisplayListAccumulator { + /// Creates a `ChildDisplayListAccumulator` from the `overflow` property in the given style. + fn new(style: &ComputedValues, bounds: Rect<Au>, 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 + } +} |