layout: Take padding into account for inline boxes

This commit is contained in:
Patrick Walton 2014-03-28 13:00:36 -07:00
parent 9874d0f9d3
commit e6665a2f14
4 changed files with 155 additions and 112 deletions

View file

@ -722,8 +722,8 @@ impl BlockFlow {
/// This is where we use the preferred widths and minimum widths /// This is where we use the preferred widths and minimum widths
/// calculated in the bubble-widths traversal. /// calculated in the bubble-widths traversal.
fn get_shrink_to_fit_width(&self, available_width: Au) -> Au { fn get_shrink_to_fit_width(&self, available_width: Au) -> Au {
geometry::min(self.base.pref_width, geometry::min(self.base.intrinsic_widths.preferred_width,
geometry::max(self.base.min_width, available_width)) geometry::max(self.base.intrinsic_widths.minimum_width, available_width))
} }
/// Collect and update static y-offsets bubbled up by kids. /// Collect and update static y-offsets bubbled up by kids.
@ -1542,20 +1542,22 @@ impl Flow for BlockFlow {
min/pref widths based on child context widths and dimensions of min/pref widths based on child context widths and dimensions of
any boxes it is responsible for flowing. */ any boxes it is responsible for flowing. */
/* TODO: absolute contexts */
/* TODO: inline-blocks */ /* TODO: inline-blocks */
fn bubble_widths(&mut self, _: &mut LayoutContext) { fn bubble_widths(&mut self, _: &mut LayoutContext) {
let mut min_width = Au::new(0);
let mut pref_width = Au::new(0);
let mut num_floats = 0; let mut num_floats = 0;
/* find max width from child block contexts */ // Find the maximum width from children.
let mut intrinsic_widths = IntrinsicWidths::new();
for child_ctx in self.base.child_iter() { for child_ctx in self.base.child_iter() {
assert!(child_ctx.is_block_flow() || child_ctx.is_inline_flow() || child_ctx.is_table_kind()); assert!(child_ctx.is_block_flow() || child_ctx.is_inline_flow() || child_ctx.is_table_kind());
let child_base = flow::mut_base(child_ctx); let child_base = flow::mut_base(child_ctx);
min_width = geometry::max(min_width, child_base.min_width); intrinsic_widths.minimum_width =
pref_width = geometry::max(pref_width, child_base.pref_width); geometry::max(intrinsic_widths.minimum_width,
child_base.intrinsic_widths.total_minimum_width());
intrinsic_widths.preferred_width =
geometry::max(intrinsic_widths.preferred_width,
child_base.intrinsic_widths.total_preferred_width());
num_floats = num_floats + child_base.num_floats; num_floats = num_floats + child_base.num_floats;
} }
@ -1566,21 +1568,22 @@ impl Flow for BlockFlow {
self.base.num_floats = num_floats; self.base.num_floats = num_floats;
} }
/* if not an anonymous block context, add in block box's widths. // Add in borders, padding, and margins.
these widths will not include child elements, just padding etc. */
for box_ in self.box_.iter() { for box_ in self.box_.iter() {
{ {
// Can compute border width here since it doesn't depend on anything. // Can compute border width here since it doesn't depend on anything.
box_.compute_borders(box_.style()) box_.compute_borders(box_.style())
} }
let (this_minimum_width, this_preferred_width) = box_.minimum_and_preferred_widths(); let box_intrinsic_widths = box_.intrinsic_widths();
min_width = min_width + this_minimum_width; intrinsic_widths.minimum_width = geometry::max(intrinsic_widths.minimum_width,
pref_width = pref_width + this_preferred_width; box_intrinsic_widths.minimum_width);
intrinsic_widths.preferred_width = geometry::max(intrinsic_widths.preferred_width,
box_intrinsic_widths.preferred_width);
intrinsic_widths.surround_width = box_intrinsic_widths.surround_width
} }
self.base.min_width = min_width; self.base.intrinsic_widths = intrinsic_widths
self.base.pref_width = pref_width;
} }
/// Recursively (top-down) determines the actual width of child contexts and boxes. When called /// Recursively (top-down) determines the actual width of child contexts and boxes. When called

View file

@ -4,17 +4,29 @@
//! The `Box` type, which represents the leaves of the layout tree. //! The `Box` type, which represents the leaves of the layout tree.
use css::node_style::StyledNode;
use layout::construct::FlowConstructor;
use layout::context::LayoutContext;
use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo, ToGfxColor};
use layout::floats::{ClearBoth, ClearLeft, ClearRight, ClearType};
use layout::flow::{Flow, FlowFlagsInfo};
use layout::flow;
use layout::model::{Auto, IntrinsicWidths, MaybeAuto, Specified, specified};
use layout::model;
use layout::util::OpaqueNodeMethods;
use layout::wrapper::{TLayoutNode, ThreadSafeLayoutNode};
use extra::url::Url; use extra::url::Url;
use sync::{MutexArc, Arc}; use sync::{MutexArc, Arc};
use geom::{Point2D, Rect, Size2D, SideOffsets2D}; use geom::{Point2D, Rect, Size2D, SideOffsets2D};
use geom::approxeq::ApproxEq; use geom::approxeq::ApproxEq;
use gfx::color::rgb; use gfx::color::rgb;
use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass}; use gfx::display_list::{BackgroundAndBorderLevel, BaseDisplayItem, BorderDisplayItem};
use gfx::display_list::{LineDisplayItem, LineDisplayItemClass}; use gfx::display_list::{BorderDisplayItemClass, ClipDisplayItem, ClipDisplayItemClass};
use gfx::display_list::{ImageDisplayItem, ImageDisplayItemClass}; use gfx::display_list::{DisplayList, ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem};
use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, TextDisplayItem}; use gfx::display_list::{LineDisplayItemClass, OpaqueNode, SolidColorDisplayItem};
use gfx::display_list::{TextDisplayItemClass, TextDisplayItemFlags, ClipDisplayItem}; use gfx::display_list::{SolidColorDisplayItemClass, StackingContext, TextDisplayItem};
use gfx::display_list::{ClipDisplayItemClass, DisplayListCollection}; use gfx::display_list::{TextDisplayItemClass, TextDisplayItemFlags};
use gfx::font::FontStyle; use gfx::font::FontStyle;
use gfx::text::text_run::TextRun; use gfx::text::text_run::TextRun;
use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg, PipelineId, SubpageId}; use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg, PipelineId, SubpageId};
@ -24,26 +36,16 @@ use servo_util::geometry::Au;
use servo_util::geometry; use servo_util::geometry;
use servo_util::range::*; use servo_util::range::*;
use servo_util::namespace; use servo_util::namespace;
use servo_util::smallvec::{SmallVec, SmallVec0};
use servo_util::str::is_whitespace; use servo_util::str::is_whitespace;
use std::cast; use std::cast;
use std::cell::RefCell; use std::cell::RefCell;
use std::num::Zero; use std::num::Zero;
use style::{ComputedValues, TElement, TNode, cascade, initial_values}; use style::{ComputedValues, TElement, TNode, cascade, initial_values};
use style::computed_values::{LengthOrPercentage, LengthOrPercentageOrAuto, overflow, LPA_Auto}; use style::computed_values::{LengthOrPercentage, LengthOrPercentageOrAuto, overflow, LPA_Auto};
use style::computed_values::{border_style, clear, font_family, line_height, position}; use style::computed_values::{background_attachment, background_repeat, border_style, clear};
use style::computed_values::{text_align, text_decoration, vertical_align, visibility, white_space}; use style::computed_values::{font_family, line_height, position, text_align, text_decoration};
use style::computed_values::{vertical_align, visibility, white_space};
use css::node_style::StyledNode;
use layout::construct::FlowConstructor;
use layout::context::LayoutContext;
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor};
use layout::floats::{ClearBoth, ClearLeft, ClearRight, ClearType};
use layout::flow::{Flow, FlowFlagsInfo};
use layout::flow;
use layout::model::{MaybeAuto, specified, Auto, Specified};
use layout::util::OpaqueNode;
use layout::wrapper::{TLayoutNode, ThreadSafeLayoutNode};
/// Boxes (`struct Box`) are the leaves of the layout tree. They cannot position themselves. In /// Boxes (`struct Box`) are the leaves of the layout tree. They cannot position themselves. In
/// general, boxes do not have a simple correspondence with CSS boxes in the specification: /// general, boxes do not have a simple correspondence with CSS boxes in the specification:
@ -288,18 +290,21 @@ pub enum SplitBoxResult {
} }
/// data for inline boxes /// Data for inline boxes.
///
/// FIXME(pcwalton): Copying `InlineParentInfo` vectors all the time is really inefficient. Use
/// atomic reference counting instead.
#[deriving(Clone)] #[deriving(Clone)]
pub struct InlineInfo { pub struct InlineInfo {
parent_info: ~[InlineParentInfo], parent_info: SmallVec0<InlineParentInfo>,
baseline: Au, baseline: Au,
} }
impl InlineInfo { impl InlineInfo {
pub fn new() -> InlineInfo { pub fn new() -> InlineInfo {
InlineInfo { InlineInfo {
parent_info: ~[], parent_info: SmallVec0::new(),
baseline: Au::new(0), baseline: Au(0),
} }
} }
} }
@ -413,17 +418,37 @@ def_noncontent!(bottom, noncontent_bottom, noncontent_inline_bottom)
def_noncontent_horiz!(left, merge_noncontent_inline_left, clear_noncontent_inline_left) def_noncontent_horiz!(left, merge_noncontent_inline_left, clear_noncontent_inline_left)
def_noncontent_horiz!(right, merge_noncontent_inline_right, clear_noncontent_inline_right) def_noncontent_horiz!(right, merge_noncontent_inline_right, clear_noncontent_inline_right)
/// Some DOM nodes can contribute more than one type of box. We call these boxes "sub-boxes". For
/// these nodes, this enum is used to determine which sub-box to construct for that node.
pub enum SubBoxKind {
/// The main box for this node. All DOM nodes that are rendered at all have at least a main
/// box.
MainBoxKind,
}
impl Box { impl Box {
/// Constructs a new `Box` instance. /// Constructs a new `Box` instance for the given node.
pub fn new(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> Box { ///
/// Arguments:
///
/// * `constructor`: The flow constructor.
///
/// * `node`: The node to create a box for.
///
/// * `sub_box_kind`: The kind of box to create for the node, in case this node can
/// contribute more than one type of box. See the definition of `SubBoxKind`.
pub fn new(constructor: &mut FlowConstructor,
node: &ThreadSafeLayoutNode,
sub_box_kind: SubBoxKind)
-> Box {
Box { Box {
node: OpaqueNode::from_thread_safe_layout_node(node), node: OpaqueNodeMethods::from_thread_safe_layout_node(node),
style: node.style().clone(), style: node.style().clone(),
border_box: RefCell::new(Au::zero_rect()), border_box: RefCell::new(Au::zero_rect()),
border: RefCell::new(Zero::zero()), border: RefCell::new(Zero::zero()),
padding: RefCell::new(Zero::zero()), padding: RefCell::new(Zero::zero()),
margin: RefCell::new(Zero::zero()), margin: RefCell::new(Zero::zero()),
specific: constructor.build_specific_box_info_for_node(node), specific: constructor.build_specific_box_info_for_node(node, sub_box_kind),
position_offsets: RefCell::new(Zero::zero()), position_offsets: RefCell::new(Zero::zero()),
inline_info: RefCell::new(None), inline_info: RefCell::new(None),
new_line_pos: ~[], new_line_pos: ~[],
@ -433,7 +458,7 @@ impl Box {
/// Constructs a new `Box` instance from a specific info. /// Constructs a new `Box` instance from a specific info.
pub fn new_from_specific_info(node: &ThreadSafeLayoutNode, specific: SpecificBoxInfo) -> Box { pub fn new_from_specific_info(node: &ThreadSafeLayoutNode, specific: SpecificBoxInfo) -> Box {
Box { Box {
node: OpaqueNode::from_thread_safe_layout_node(node), node: OpaqueNodeMethods::from_thread_safe_layout_node(node),
style: node.style().clone(), style: node.style().clone(),
border_box: RefCell::new(Au::zero_rect()), border_box: RefCell::new(Au::zero_rect()),
border: RefCell::new(Zero::zero()), border: RefCell::new(Zero::zero()),
@ -460,7 +485,7 @@ impl Box {
let (node_style, _) = cascade(&[], false, Some(node.style().get()), let (node_style, _) = cascade(&[], false, Some(node.style().get()),
&initial_values(), None); &initial_values(), None);
Box { Box {
node: OpaqueNode::from_thread_safe_layout_node(node), node: OpaqueNodeMethods::from_thread_safe_layout_node(node),
style: Arc::new(node_style), style: Arc::new(node_style),
border_box: RefCell::new(Au::zero_rect()), border_box: RefCell::new(Au::zero_rect()),
border: RefCell::new(Zero::zero()), border: RefCell::new(Zero::zero()),
@ -589,46 +614,45 @@ impl Box {
} }
} }
/// Returns the shared part of the width for computation of minimum and preferred width per /// Uses the style only to estimate the intrinsic widths. These may be modified for text or
/// CSS 2.1. /// replaced elements.
fn guess_width(&self) -> Au { fn style_specified_intrinsic_width(&self) -> IntrinsicWidths {
let (use_margins, use_padding) = match self.specific {
GenericBox | IframeBox(_) | ImageBox(_) => (true, true),
TableBox | TableCellBox => (false, true),
TableWrapperBox => (true, false),
TableRowBox => (false, false),
ScannedTextBox(_) | TableColumnBox(_) | UnscannedTextBox(_) => {
// Styles are irrelevant for these kinds of boxes.
return IntrinsicWidths::new()
}
};
let style = self.style(); let style = self.style();
let mut margin_left = Au::new(0);
let mut margin_right = Au::new(0);
let mut padding_left = Au::new(0);
let mut padding_right = Au::new(0);
match self.specific {
GenericBox | IframeBox(_) | ImageBox(_) => {
margin_left = MaybeAuto::from_style(style.Margin.get().margin_left,
Au::new(0)).specified_or_zero();
margin_right = MaybeAuto::from_style(style.Margin.get().margin_right,
Au::new(0)).specified_or_zero();
padding_left = self.compute_padding_length(style.Padding.get().padding_left,
Au::new(0));
padding_right = self.compute_padding_length(style.Padding.get().padding_right,
Au::new(0));
}
TableBox | TableCellBox => {
padding_left = self.compute_padding_length(style.Padding.get().padding_left,
Au::new(0));
padding_right = self.compute_padding_length(style.Padding.get().padding_right,
Au::new(0));
}
TableWrapperBox => {
margin_left = MaybeAuto::from_style(style.Margin.get().margin_left,
Au::new(0)).specified_or_zero();
margin_right = MaybeAuto::from_style(style.Margin.get().margin_right,
Au::new(0)).specified_or_zero();
}
TableRowBox => {}
ScannedTextBox(_) | TableColumnBox(_) | UnscannedTextBox(_) => return Au(0),
}
let width = MaybeAuto::from_style(style.Box.get().width, Au::new(0)).specified_or_zero(); let width = MaybeAuto::from_style(style.Box.get().width, Au::new(0)).specified_or_zero();
width + margin_left + margin_right + padding_left + padding_right + let (mut margin_left, mut margin_right) = (Au(0), Au(0));
self.border.get().left + self.border.get().right if use_margins {
margin_left = MaybeAuto::from_style(style.Margin.get().margin_left,
Au(0)).specified_or_zero();
margin_right = MaybeAuto::from_style(style.Margin.get().margin_right,
Au(0)).specified_or_zero();
}
let (mut padding_left, mut padding_right) = (Au(0), Au(0));
if use_padding {
padding_left = self.compute_padding_length(style.Padding.get().padding_left, Au(0));
padding_right = self.compute_padding_length(style.Padding.get().padding_right, Au(0));
}
let surround_width = margin_left + margin_right + padding_left + padding_right +
self.border.get().left + self.border.get().right;
IntrinsicWidths {
minimum_width: width,
preferred_width: width,
surround_width: surround_width,
}
} }
pub fn calculate_line_height(&self, font_size: Au) -> Au { pub fn calculate_line_height(&self, font_size: Au) -> Au {
@ -1315,7 +1339,7 @@ impl Box {
let inline_info = self.inline_info.borrow(); let inline_info = self.inline_info.borrow();
match inline_info.get() { match inline_info.get() {
&Some(ref info) => { &Some(ref info) => {
for data in info.parent_info.rev_iter() { for data in info.parent_info.as_slice().rev_iter() {
let parent_info = FlowFlagsInfo::new(data.style.get()); let parent_info = FlowFlagsInfo::new(data.style.get());
flow_flags.propagate_text_decoration_from_parent(&parent_info); flow_flags.propagate_text_decoration_from_parent(&parent_info);
} }
@ -1430,18 +1454,19 @@ impl Box {
} }
_ => {} _ => {}
} }
} }
/// Returns the *minimum width* and *preferred width* of this box as defined by CSS 2.1. /// Returns the intrinsic widths of this fragment.
pub fn minimum_and_preferred_widths(&self) -> (Au, Au) { pub fn intrinsic_widths(&self) -> IntrinsicWidths {
let guessed_width = self.guess_width(); let mut result = self.style_specified_intrinsic_width();
let (additional_minimum, additional_preferred) = match self.specific {
GenericBox | IframeBox(_) | TableBox | TableCellBox | TableColumnBox(_) | match self.specific {
TableRowBox | TableWrapperBox => (Au(0), Au(0)), GenericBox | IframeBox(_) | TableBox | TableCellBox | TableColumnBox(_) | TableRowBox |
TableWrapperBox => {}
ImageBox(ref image_box_info) => { ImageBox(ref image_box_info) => {
let image_width = image_box_info.image_width(); let image_width = image_box_info.image_width();
(image_width, image_width) result.minimum_width = geometry::max(result.minimum_width, image_width);
result.preferred_width = geometry::max(result.preferred_width, image_width);
} }
ScannedTextBox(ref text_box_info) => { ScannedTextBox(ref text_box_info) => {
let range = &text_box_info.range; let range = &text_box_info.range;
@ -1453,11 +1478,29 @@ impl Box {
max_line_width = Au::max(max_line_width, line_metrics.advance_width); max_line_width = Au::max(max_line_width, line_metrics.advance_width);
} }
(min_line_width, max_line_width) result.minimum_width = geometry::max(result.minimum_width, min_line_width);
result.preferred_width = geometry::max(result.preferred_width, max_line_width);
} }
UnscannedTextBox(..) => fail!("Unscanned text boxes should have been scanned by now!"), UnscannedTextBox(..) => fail!("Unscanned text boxes should have been scanned by now!"),
}; }
(guessed_width + additional_minimum, guessed_width + additional_preferred)
// Take borders and padding for parent inline boxes into account.
let inline_info = self.inline_info.get();
match inline_info {
None => {}
Some(ref inline_info) => {
for inline_parent_info in inline_info.parent_info.iter() {
let border_width = inline_parent_info.border.left +
inline_parent_info.border.right;
let padding_width = inline_parent_info.padding.left +
inline_parent_info.padding.right;
result.minimum_width = result.minimum_width + border_width + padding_width;
result.preferred_width = result.preferred_width + border_width + padding_width;
}
}
}
result
} }
@ -1663,16 +1706,16 @@ impl Box {
} }
} }
/// Assigns replaced width for this box only if it is replaced content. /// Assigns replaced width, padding, and margins for this box only if it is replaced content
/// /// per CSS 2.1 § 10.3.2.
/// This assigns only the width, not margin or anything else. pub fn assign_replaced_width_if_necessary(&self, container_width: Au) {
/// CSS 2.1 § 10.3.2.
pub fn assign_replaced_width_if_necessary(&self,container_width: Au) {
match self.specific { match self.specific {
GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox | GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox |
TableWrapperBox => {} TableWrapperBox => {}
ImageBox(ref image_box_info) => { ImageBox(ref image_box_info) => {
// TODO(ksh8281): compute border,margin,padding self.compute_padding(self.style(), container_width);
// TODO(ksh8281): compute border,margin
let width = ImageBoxInfo::style_length(self.style().Box.get().width, let width = ImageBoxInfo::style_length(self.style().Box.get().width,
image_box_info.dom_width, image_box_info.dom_width,
container_width); container_width);

View file

@ -814,8 +814,7 @@ impl BaseFlow {
next_sibling: None, next_sibling: None,
prev_sibling: Rawlink::none(), prev_sibling: Rawlink::none(),
min_width: Au::new(0), intrinsic_widths: IntrinsicWidths::new(),
pref_width: Au::new(0),
position: Au::zero_rect(), position: Au::zero_rect(),
overflow: Au::zero_rect(), overflow: Au::zero_rect(),

View file

@ -21,7 +21,6 @@ use gfx::display_list::{ContentLevel, StackingContext};
use servo_util::geometry::Au; use servo_util::geometry::Au;
use servo_util::geometry; use servo_util::geometry;
use servo_util::range::Range; use servo_util::range::Range;
use std::cell::RefCell;
use std::mem; use std::mem;
use std::u16; use std::u16;
use style::computed_values::{text_align, vertical_align, white_space}; use style::computed_values::{text_align, vertical_align, white_space};
@ -630,20 +629,19 @@ impl Flow for InlineFlow {
child_base.floats = Floats::new(); child_base.floats = Floats::new();
} }
let mut min_width = Au::new(0); let mut intrinsic_widths = IntrinsicWidths::new();
let mut pref_width = Au::new(0);
for box_ in self.boxes.iter() { for box_ in self.boxes.iter() {
debug!("Flow: measuring {:s}", box_.debug_str()); debug!("Flow: measuring {:s}", box_.debug_str());
box_.compute_borders(box_.style()); box_.compute_borders(box_.style());
let (this_minimum_width, this_preferred_width) =
box_.minimum_and_preferred_widths(); let box_intrinsic_widths = box_.intrinsic_widths();
min_width = Au::max(min_width, this_minimum_width); intrinsic_widths.minimum_width = geometry::max(intrinsic_widths.minimum_width,
pref_width = Au::max(pref_width, this_preferred_width); box_intrinsic_widths.minimum_width);
intrinsic_widths.preferred_width = geometry::max(intrinsic_widths.preferred_width,
box_intrinsic_widths.preferred_width);
} }
self.base.min_width = min_width; self.base.intrinsic_widths = intrinsic_widths;
self.base.pref_width = pref_width;
self.base.num_floats = num_floats; self.base.num_floats = num_floats;
} }