aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout/fragment.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout/fragment.rs')
-rw-r--r--components/layout/fragment.rs1597
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
+ }
+}