/* 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/. */ //! Layout for CSS block-level elements. //! //! As a terminology note, the term *absolute positioning* here refers to elements with position //! `absolute` or `fixed`. The term *positioned element* refers to elements with position //! `relative`, `absolute`, and `fixed`. The term *containing block* (occasionally abbreviated as //! *CB*) is the containing block for the current flow, which differs from the static containing //! block if the flow is absolutely-positioned. //! //! "CSS 2.1" or "CSS 2.2" refers to the editor's draft of the W3C "Cascading Style Sheets Level 2 //! Revision 2 (CSS 2.2) Specification" available here: //! //! http://dev.w3.org/csswg/css2/ //! //! "INTRINSIC" refers to L. David Baron's "More Precise Definitions of Inline Layout and Table //! Layout" available here: //! //! http://dbaron.org/css/intrinsic/ //! //! "CSS-SIZING" refers to the W3C "CSS Intrinsic & Extrinsic Sizing Module Level 3" document //! available here: //! //! http://dev.w3.org/csswg/css-sizing/ #![deny(unsafe_code)] use app_units::{Au, MAX_AU}; use context::LayoutContext; use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode}; use display_list_builder::{FragmentDisplayListBuilding}; use euclid::{Point2D, Rect, Size2D}; use floats::{ClearType, FloatKind, Floats, PlacementInfo}; use flow::{BLOCK_POSITION_IS_STATIC}; use flow::{CLEARS_LEFT, CLEARS_RIGHT}; use flow::{HAS_LEFT_FLOATED_DESCENDANTS, HAS_RIGHT_FLOATED_DESCENDANTS}; use flow::{IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS, INLINE_POSITION_IS_STATIC}; use flow::{IS_ABSOLUTELY_POSITIONED}; use flow::{ImmutableFlowUtils, LateAbsolutePositionInfo, MutableFlowUtils, OpaqueFlow}; use flow::{NEEDS_LAYER, PostorderFlowTraversal, PreorderFlowTraversal, mut_base}; use flow::{self, BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, ForceNonfloatedFlag}; use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, HAS_LAYER}; use fragment::{SpecificFragmentInfo}; use gfx::display_list::{ClippingRegion, DisplayList}; use gfx_traits::LayerId; use incremental::{REFLOW, REFLOW_OUT_OF_FLOW}; use layout_debug; use layout_task::DISPLAY_PORT_SIZE_FACTOR; use model::{CollapsibleMargins, MaybeAuto, specified, specified_or_none}; use model::{IntrinsicISizes, MarginCollapseInfo}; use rustc_serialize::{Encodable, Encoder}; use std::cmp::{max, min}; use std::fmt; use std::sync::Arc; use style::computed_values::{border_collapse, box_sizing, display, float, overflow_x, overflow_y}; use style::computed_values::{position, text_align, transform_style}; use style::properties::ComputedValues; use style::values::computed::{LengthOrNone, LengthOrPercentageOrNone}; use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto}; use util::geometry::MAX_RECT; use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode}; use util::opts; use util::print_tree::PrintTree; /// Information specific to floated blocks. #[derive(Clone, RustcEncodable)] pub struct FloatedBlockInfo { /// The amount of inline size that is available for the float. pub containing_inline_size: Au, /// The float ceiling, relative to `BaseFlow::position::cur_b` (i.e. the top part of the border /// box). pub float_ceiling: Au, /// Left or right? pub float_kind: FloatKind, } impl FloatedBlockInfo { pub fn new(float_kind: FloatKind) -> FloatedBlockInfo { FloatedBlockInfo { containing_inline_size: Au(0), float_ceiling: Au(0), float_kind: float_kind, } } } /// The solutions for the block-size-and-margins constraint equation. #[derive(Copy, Clone)] struct BSizeConstraintSolution { block_start: Au, block_size: Au, margin_block_start: Au, margin_block_end: Au } impl BSizeConstraintSolution { fn new(block_start: Au, block_size: Au, margin_block_start: Au, margin_block_end: Au) -> BSizeConstraintSolution { BSizeConstraintSolution { block_start: block_start, block_size: block_size, margin_block_start: margin_block_start, margin_block_end: margin_block_end, } } /// Solve the vertical constraint equation for absolute non-replaced elements. /// /// CSS Section 10.6.4 /// Constraint equation: /// block-start + block-end + block-size + margin-block-start + margin-block-end /// = absolute containing block block-size - (vertical padding and border) /// [aka available_block-size] /// /// Return the solution for the equation. fn solve_vertical_constraints_abs_nonreplaced(block_size: MaybeAuto, block_start_margin: MaybeAuto, block_end_margin: MaybeAuto, block_start: MaybeAuto, block_end: MaybeAuto, content_block_size: Au, available_block_size: Au) -> BSizeConstraintSolution { let (block_start, block_size, margin_block_start, margin_block_end) = match (block_start, block_end, block_size) { (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); // Now it is the same situation as block-start Specified and block-end // and block-size Auto. let block_size = content_block_size; // Use a dummy value for `block_start`, since it has the static position. (Au(0), block_size, margin_block_start, margin_block_end) } (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end), MaybeAuto::Specified(block_size)) => { match (block_start_margin, block_end_margin) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let total_margin_val = available_block_size - block_start - block_end - block_size; (block_start, block_size, total_margin_val.scale_by(0.5), total_margin_val.scale_by(0.5)) } (MaybeAuto::Specified(margin_block_start), MaybeAuto::Auto) => { let sum = block_start + block_end + block_size + margin_block_start; (block_start, block_size, margin_block_start, available_block_size - sum) } (MaybeAuto::Auto, MaybeAuto::Specified(margin_block_end)) => { let sum = block_start + block_end + block_size + margin_block_end; (block_start, block_size, available_block_size - sum, margin_block_end) } (MaybeAuto::Specified(margin_block_start), MaybeAuto::Specified(margin_block_end)) => { // Values are over-constrained. Ignore value for 'block-end'. (block_start, block_size, margin_block_start, margin_block_end) } } } // For the rest of the cases, auto values for margin are set to 0 // If only one is Auto, solve for it (MaybeAuto::Auto, MaybeAuto::Specified(block_end), MaybeAuto::Specified(block_size)) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); let sum = block_end + block_size + margin_block_start + margin_block_end; (available_block_size - sum, block_size, margin_block_start, margin_block_end) } (MaybeAuto::Specified(block_start), MaybeAuto::Auto, MaybeAuto::Specified(block_size)) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); (block_start, block_size, margin_block_start, margin_block_end) } (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end), MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); let sum = block_start + block_end + margin_block_start + margin_block_end; (block_start, available_block_size - sum, margin_block_start, margin_block_end) } // If block-size is auto, then block-size is content block-size. Solve for the // non-auto value. (MaybeAuto::Specified(block_start), MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); let block_size = content_block_size; (block_start, block_size, margin_block_start, margin_block_end) } (MaybeAuto::Auto, MaybeAuto::Specified(block_end), MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); let block_size = content_block_size; let sum = block_end + block_size + margin_block_start + margin_block_end; (available_block_size - sum, block_size, margin_block_start, margin_block_end) } (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(block_size)) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); // Use a dummy value for `block_start`, since it has the static position. (Au(0), block_size, margin_block_start, margin_block_end) } }; BSizeConstraintSolution::new(block_start, block_size, margin_block_start, margin_block_end) } /// Solve the vertical constraint equation for absolute replaced elements. /// /// Assumption: The used value for block-size has already been calculated. /// /// CSS Section 10.6.5 /// Constraint equation: /// block-start + block-end + block-size + margin-block-start + margin-block-end /// = absolute containing block block-size - (vertical padding and border) /// [aka available block-size] /// /// Return the solution for the equation. fn solve_vertical_constraints_abs_replaced(block_size: Au, block_start_margin: MaybeAuto, block_end_margin: MaybeAuto, block_start: MaybeAuto, block_end: MaybeAuto, _: Au, available_block_size: Au) -> BSizeConstraintSolution { let (block_start, block_size, margin_block_start, margin_block_end) = match (block_start, block_end) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); // Use a dummy value for `block_start`, since it has the static position. (Au(0), block_size, margin_block_start, margin_block_end) } (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end)) => { match (block_start_margin, block_end_margin) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let total_margin_val = available_block_size - block_start - block_end - block_size; (block_start, block_size, total_margin_val.scale_by(0.5), total_margin_val.scale_by(0.5)) } (MaybeAuto::Specified(margin_block_start), MaybeAuto::Auto) => { let sum = block_start + block_end + block_size + margin_block_start; (block_start, block_size, margin_block_start, available_block_size - sum) } (MaybeAuto::Auto, MaybeAuto::Specified(margin_block_end)) => { let sum = block_start + block_end + block_size + margin_block_end; (block_start, block_size, available_block_size - sum, margin_block_end) } (MaybeAuto::Specified(margin_block_start), MaybeAuto::Specified(margin_block_end)) => { // Values are over-constrained. Ignore value for 'block-end'. (block_start, block_size, margin_block_start, margin_block_end) } } } // If only one is Auto, solve for it (MaybeAuto::Auto, MaybeAuto::Specified(block_end)) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); let sum = block_end + block_size + margin_block_start + margin_block_end; (available_block_size - sum, block_size, margin_block_start, margin_block_end) } (MaybeAuto::Specified(block_start), MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); (block_start, block_size, margin_block_start, margin_block_end) } }; BSizeConstraintSolution::new(block_start, block_size, margin_block_start, margin_block_end) } } /// Performs block-size calculations potentially multiple times, taking /// (assuming an horizontal writing mode) `height`, `min-height`, and `max-height` /// into account. After each call to `next()`, the caller must call `.try()` with the /// current calculated value of `height`. /// /// See CSS 2.1 § 10.7. pub struct CandidateBSizeIterator { block_size: MaybeAuto, max_block_size: Option, min_block_size: Au, pub candidate_value: Au, status: CandidateBSizeIteratorStatus, } impl CandidateBSizeIterator { /// Creates a new candidate block-size iterator. `block_container_block-size` is `None` if the block-size /// of the block container has not been determined yet. It will always be `Some` in the case of /// absolutely-positioned containing blocks. pub fn new(fragment: &Fragment, block_container_block_size: Option) -> CandidateBSizeIterator { // Per CSS 2.1 § 10.7, (assuming an horizontal writing mode,) // percentages in `min-height` and `max-height` refer to the height of // the containing block. // If that is not determined yet by the time we need to resolve // `min-height` and `max-height`, percentage values are ignored. let block_size = match (fragment.style.content_block_size(), block_container_block_size) { (LengthOrPercentageOrAuto::Percentage(percent), Some(block_container_block_size)) => { MaybeAuto::Specified(block_container_block_size.scale_by(percent)) } (LengthOrPercentageOrAuto::Calc(calc), Some(block_container_block_size)) => { MaybeAuto::Specified(calc.length() + block_container_block_size.scale_by(calc.percentage())) } (LengthOrPercentageOrAuto::Percentage(_), None) | (LengthOrPercentageOrAuto::Auto, _) | (LengthOrPercentageOrAuto::Calc(_), _) => MaybeAuto::Auto, (LengthOrPercentageOrAuto::Length(length), _) => MaybeAuto::Specified(length), }; let max_block_size = match (fragment.style.max_block_size(), block_container_block_size) { (LengthOrPercentageOrNone::Percentage(percent), Some(block_container_block_size)) => { Some(block_container_block_size.scale_by(percent)) } (LengthOrPercentageOrNone::Calc(calc), Some(block_container_block_size)) => { Some(block_container_block_size.scale_by(calc.percentage()) + calc.length()) } (LengthOrPercentageOrNone::Calc(_), _) | (LengthOrPercentageOrNone::Percentage(_), None) | (LengthOrPercentageOrNone::None, _) => None, (LengthOrPercentageOrNone::Length(length), _) => Some(length), }; let min_block_size = match (fragment.style.min_block_size(), block_container_block_size) { (LengthOrPercentage::Percentage(percent), Some(block_container_block_size)) => { block_container_block_size.scale_by(percent) } (LengthOrPercentage::Calc(calc), Some(block_container_block_size)) => { calc.length() + block_container_block_size.scale_by(calc.percentage()) } (LengthOrPercentage::Calc(calc), None) => calc.length(), (LengthOrPercentage::Percentage(_), None) => Au(0), (LengthOrPercentage::Length(length), _) => length, }; // If the style includes `box-sizing: border-box`, subtract the border and padding. let adjustment_for_box_sizing = match fragment.style.get_box().box_sizing { box_sizing::T::border_box => fragment.border_padding.block_start_end(), box_sizing::T::content_box => Au(0), }; return CandidateBSizeIterator { block_size: block_size.map(|size| adjust(size, adjustment_for_box_sizing)), max_block_size: max_block_size.map(|size| adjust(size, adjustment_for_box_sizing)), min_block_size: adjust(min_block_size, adjustment_for_box_sizing), candidate_value: Au(0), status: CandidateBSizeIteratorStatus::Initial, }; fn adjust(size: Au, delta: Au) -> Au { max(size - delta, Au(0)) } } } impl Iterator for CandidateBSizeIterator { type Item = MaybeAuto; fn next(&mut self) -> Option { self.status = match self.status { CandidateBSizeIteratorStatus::Initial => CandidateBSizeIteratorStatus::Trying, CandidateBSizeIteratorStatus::Trying => { match self.max_block_size { Some(max_block_size) if self.candidate_value > max_block_size => { CandidateBSizeIteratorStatus::TryingMax } _ if self.candidate_value < self.min_block_size => { CandidateBSizeIteratorStatus::TryingMin } _ => CandidateBSizeIteratorStatus::Found, } } CandidateBSizeIteratorStatus::TryingMax => { if self.candidate_value < self.min_block_size { CandidateBSizeIteratorStatus::TryingMin } else { CandidateBSizeIteratorStatus::Found } } CandidateBSizeIteratorStatus::TryingMin | CandidateBSizeIteratorStatus::Found => { CandidateBSizeIteratorStatus::Found } }; match self.status { CandidateBSizeIteratorStatus::Trying => Some(self.block_size), CandidateBSizeIteratorStatus::TryingMax => { Some(MaybeAuto::Specified(self.max_block_size.unwrap())) } CandidateBSizeIteratorStatus::TryingMin => { Some(MaybeAuto::Specified(self.min_block_size)) } CandidateBSizeIteratorStatus::Found => None, CandidateBSizeIteratorStatus::Initial => panic!(), } } } enum CandidateBSizeIteratorStatus { Initial, Trying, TryingMax, TryingMin, Found, } // A helper function used in block-size calculation. fn translate_including_floats(cur_b: &mut Au, delta: Au, floats: &mut Floats) { *cur_b = *cur_b + delta; let writing_mode = floats.writing_mode; floats.translate(LogicalSize::new(writing_mode, Au(0), -delta)); } /// The real assign-block-sizes traversal for flows with position 'absolute'. /// /// This is a traversal of an Absolute Flow tree. /// - Relatively positioned flows and the Root flow start new Absolute flow trees. /// - The kids of a flow in this tree will be the flows for which it is the /// absolute Containing Block. /// - Thus, leaf nodes and inner non-root nodes are all Absolute Flows. /// /// A Flow tree can have several Absolute Flow trees (depending on the number /// of relatively positioned flows it has). /// /// Note that flows with position 'fixed' just form a flat list as they all /// have the Root flow as their CB. pub struct AbsoluteAssignBSizesTraversal<'a>(pub &'a LayoutContext<'a>); impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> { #[inline] fn process(&self, flow: &mut Flow) { { // The root of the absolute flow tree is definitely not absolutely // positioned. Nothing to process here. let flow: &Flow = flow; if flow.contains_roots_of_absolute_flow_tree() { return; } if !flow.is_block_like() { return } } let block = flow.as_mut_block(); debug_assert!(block.base.flags.contains(IS_ABSOLUTELY_POSITIONED)); if !block.base.restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW) { return } let AbsoluteAssignBSizesTraversal(ref layout_context) = *self; block.calculate_absolute_block_size_and_margins(*layout_context); } } pub enum BlockType { Replaced, NonReplaced, AbsoluteReplaced, AbsoluteNonReplaced, FloatReplaced, FloatNonReplaced, InlineBlockReplaced, InlineBlockNonReplaced, } #[derive(Clone, PartialEq)] pub enum MarginsMayCollapseFlag { MarginsMayCollapse, MarginsMayNotCollapse, } #[derive(PartialEq)] enum FormattingContextType { None, Block, Other, } // A block formatting context. #[derive(RustcEncodable)] pub struct BlockFlow { /// Data common to all flows. pub base: BaseFlow, /// The associated fragment. pub fragment: Fragment, /// The sum of the inline-sizes of all logically left floats that precede this block. This is /// used to speculatively lay out block formatting contexts. inline_size_of_preceding_left_floats: Au, /// The sum of the inline-sizes of all logically right floats that precede this block. This is /// used to speculatively lay out block formatting contexts. inline_size_of_preceding_right_floats: Au, /// Additional floating flow members. pub float: Option>, /// Various flags. pub flags: BlockFlowFlags, } bitflags! { flags BlockFlowFlags: u8 { #[doc = "If this is set, then this block flow is the root flow."] const IS_ROOT = 0x01, } } impl Encodable for BlockFlowFlags { fn encode(&self, e: &mut S) -> Result<(), S::Error> { self.bits().encode(e) } } impl BlockFlow { pub fn from_fragment(fragment: Fragment, float_kind: Option) -> BlockFlow { let writing_mode = fragment.style().writing_mode; BlockFlow { base: BaseFlow::new(Some(fragment.style()), writing_mode, match float_kind { Some(_) => ForceNonfloatedFlag::FloatIfNecessary, None => ForceNonfloatedFlag::ForceNonfloated, }), fragment: fragment, inline_size_of_preceding_left_floats: Au(0), inline_size_of_preceding_right_floats: Au(0), float: float_kind.map(|kind| box FloatedBlockInfo::new(kind)), flags: BlockFlowFlags::empty(), } } /// Return the type of this block. /// /// This determines the algorithm used to calculate inline-size, block-size, and the /// relevant margins for this Block. pub fn block_type(&self) -> BlockType { if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { if self.is_replaced_content() { BlockType::AbsoluteReplaced } else { BlockType::AbsoluteNonReplaced } } else if self.base.flags.is_float() { if self.is_replaced_content() { BlockType::FloatReplaced } else { BlockType::FloatNonReplaced } } else if self.is_inline_block() { if self.is_replaced_content() { BlockType::InlineBlockReplaced } else { BlockType::InlineBlockNonReplaced } } else { if self.is_replaced_content() { BlockType::Replaced } else { BlockType::NonReplaced } } } /// Compute the actual inline size and position for this block. pub fn compute_used_inline_size(&mut self, layout_context: &LayoutContext, containing_block_inline_size: Au) { let block_type = self.block_type(); match block_type { BlockType::AbsoluteReplaced => { let inline_size_computer = AbsoluteReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, containing_block_inline_size); } BlockType::AbsoluteNonReplaced => { let inline_size_computer = AbsoluteNonReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, containing_block_inline_size); } BlockType::FloatReplaced => { let inline_size_computer = FloatReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, containing_block_inline_size); } BlockType::FloatNonReplaced => { let inline_size_computer = FloatNonReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, containing_block_inline_size); } BlockType::InlineBlockReplaced => { let inline_size_computer = InlineBlockReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, containing_block_inline_size); } BlockType::InlineBlockNonReplaced => { let inline_size_computer = InlineBlockNonReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, containing_block_inline_size); } BlockType::Replaced => { let inline_size_computer = BlockReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, containing_block_inline_size); } BlockType::NonReplaced => { let inline_size_computer = BlockNonReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, containing_block_inline_size); } } } /// Return this flow's fragment. pub fn fragment(&mut self) -> &mut Fragment { &mut self.fragment } /// Return the size of the containing block for the given immediate absolute descendant of this /// flow. /// /// Right now, this only gets the containing block size for absolutely positioned elements. /// Note: We assume this is called in a top-down traversal, so it is ok to reference the CB. #[inline] pub fn containing_block_size(&self, viewport_size: &Size2D, descendant: OpaqueFlow) -> LogicalSize { debug_assert!(self.base.flags.contains(IS_ABSOLUTELY_POSITIONED)); if self.is_fixed() { // Initial containing block is the CB for the root LogicalSize::from_physical(self.base.writing_mode, *viewport_size) } else { self.base.absolute_cb.generated_containing_block_size(descendant) } } /// Return true if this has a replaced fragment. /// /// Text, Images, Inline Block and Canvas /// (https://html.spec.whatwg.org/multipage/#replaced-elements) fragments are considered as /// replaced fragments. fn is_replaced_content(&self) -> bool { match self.fragment.specific { SpecificFragmentInfo::ScannedText(_) | SpecificFragmentInfo::Image(_) | SpecificFragmentInfo::Canvas(_) | SpecificFragmentInfo::InlineBlock(_) => true, _ => false, } } /// Return shrink-to-fit inline-size. /// /// This is where we use the preferred inline-sizes and minimum inline-sizes /// calculated in the bubble-inline-sizes traversal. pub fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au { let content_intrinsic_inline_sizes = self.content_intrinsic_inline_sizes(); min(content_intrinsic_inline_sizes.preferred_inline_size, max(content_intrinsic_inline_sizes.minimum_inline_size, available_inline_size)) } /// If this is the root flow, shifts all kids down and adjusts our size to account for /// root flow margins, which should never be collapsed according to CSS § 8.3.1. /// /// TODO(#2017, pcwalton): This is somewhat inefficient (traverses kids twice); can we do /// better? fn adjust_fragments_for_collapsed_margins_if_root(&mut self) { if !self.is_root() { return } let (block_start_margin_value, block_end_margin_value) = match self.base.collapsible_margins { CollapsibleMargins::CollapseThrough(_) => { panic!("Margins unexpectedly collapsed through root flow.") } CollapsibleMargins::Collapse(block_start_margin, block_end_margin) => { (block_start_margin.collapse(), block_end_margin.collapse()) } CollapsibleMargins::None(block_start, block_end) => (block_start, block_end), }; // Shift all kids down (or up, if margins are negative) if necessary. if block_start_margin_value != Au(0) { for kid in self.base.child_iter() { let kid_base = flow::mut_base(kid); kid_base.position.start.b = kid_base.position.start.b + block_start_margin_value } } self.base.position.size.block = self.base.position.size.block + block_start_margin_value + block_end_margin_value; self.fragment.border_box.size.block = self.fragment.border_box.size.block + block_start_margin_value + block_end_margin_value; } /// Assign block-size for current flow. /// /// * Collapse margins for flow's children and set in-flow child flows' block offsets now that /// we know their block-sizes. /// * Calculate and set the block-size of the current flow. /// * Calculate block-size, vertical margins, and block offset for the flow's box using CSS § /// 10.6.7. /// /// For absolute flows, we store the calculated content block-size for the flow. We defer the /// calculation of the other values until a later traversal. /// /// `inline(always)` because this is only ever called by in-order or non-in-order top-level /// methods. #[inline(always)] pub fn assign_block_size_block_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>, margins_may_collapse: MarginsMayCollapseFlag) { let _scope = layout_debug_scope!("assign_block_size_block_base {:x}", self.base.debug_id()); if self.base.restyle_damage.contains(REFLOW) { self.determine_if_layer_needed(); // Our current border-box position. let mut cur_b = Au(0); // Absolute positioning establishes a block formatting context. Don't propagate floats // in or out. (But do propagate them between kids.) if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) || margins_may_collapse != MarginsMayCollapseFlag::MarginsMayCollapse { self.base.floats = Floats::new(self.fragment.style.writing_mode); } let mut margin_collapse_info = MarginCollapseInfo::new(); let writing_mode = self.base.floats.writing_mode; self.base.floats.translate(LogicalSize::new( writing_mode, -self.fragment.inline_start_offset(), Au(0))); // The sum of our block-start border and block-start padding. let block_start_offset = self.fragment.border_padding.block_start; translate_including_floats(&mut cur_b, block_start_offset, &mut self.base.floats); let can_collapse_block_start_margin_with_kids = margins_may_collapse == MarginsMayCollapseFlag::MarginsMayCollapse && !self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) && self.fragment.border_padding.block_start == Au(0); margin_collapse_info.initialize_block_start_margin( &self.fragment, can_collapse_block_start_margin_with_kids); // At this point, `cur_b` is at the content edge of our box. Now iterate over children. let mut floats = self.base.floats.clone(); let thread_id = self.base.thread_id; for kid in self.base.child_iter() { if flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED) { // Assume that the *hypothetical box* for an absolute flow starts immediately // after the block-end border edge of the previous flow. if flow::base(kid).flags.contains(BLOCK_POSITION_IS_STATIC) { flow::mut_base(kid).position.start.b = cur_b + flow::base(kid).collapsible_margins .block_start_margin_for_noncollapsible_context() } kid.place_float_if_applicable(layout_context); if !flow::base(kid).flags.is_float() { kid.assign_block_size_for_inorder_child_if_necessary(layout_context, thread_id); } // Skip the collapsing and float processing for absolute flow kids and continue // with the next flow. continue } // Assign block-size now for the child if it was impacted by floats and we couldn't // before. flow::mut_base(kid).floats = floats.clone(); if flow::base(kid).flags.is_float() { flow::mut_base(kid).position.start.b = cur_b; { let kid_block = kid.as_mut_block(); kid_block.float.as_mut().unwrap().float_ceiling = margin_collapse_info.current_float_ceiling(); } kid.place_float_if_applicable(layout_context); let kid_base = flow::mut_base(kid); floats = kid_base.floats.clone(); continue } // If we have clearance, assume there are no floats in. // // FIXME(#2008, pcwalton): This could be wrong if we have `clear: left` or `clear: // right` and there are still floats to impact, of course. But this gets // complicated with margin collapse. Possibly the right thing to do is to lay out // the block again in this rare case. (Note that WebKit can lay blocks out twice; // this may be related, although I haven't looked into it closely.) if flow::base(kid).flags.clears_floats() { flow::mut_base(kid).floats = Floats::new(self.fragment.style.writing_mode) } // Lay the child out if this was an in-order traversal. let need_to_process_child_floats = kid.assign_block_size_for_inorder_child_if_necessary(layout_context, thread_id); // Handle any (possibly collapsed) top margin. let delta = margin_collapse_info.advance_block_start_margin( &flow::base(kid).collapsible_margins); translate_including_floats(&mut cur_b, delta, &mut floats); // Clear past the floats that came in, if necessary. let clearance = match (flow::base(kid).flags.contains(CLEARS_LEFT), flow::base(kid).flags.contains(CLEARS_RIGHT)) { (false, false) => Au(0), (true, false) => floats.clearance(ClearType::Left), (false, true) => floats.clearance(ClearType::Right), (true, true) => floats.clearance(ClearType::Both), }; translate_including_floats(&mut cur_b, clearance, &mut floats); // At this point, `cur_b` is at the border edge of the child. flow::mut_base(kid).position.start.b = cur_b; // Now pull out the child's outgoing floats. We didn't do this immediately after // the `assign_block_size_for_inorder_child_if_necessary` call because clearance on // a block operates on the floats that come *in*, not the floats that go *out*. if need_to_process_child_floats { floats = flow::mut_base(kid).floats.clone() } // Move past the child's border box. Do not use the `translate_including_floats` // function here because the child has already translated floats past its border // box. let kid_base = flow::mut_base(kid); cur_b = cur_b + kid_base.position.size.block; // Handle any (possibly collapsed) block-end margin. let delta = margin_collapse_info.advance_block_end_margin(&kid_base.collapsible_margins); translate_including_floats(&mut cur_b, delta, &mut floats); } // Add in our block-end margin and compute our collapsible margins. let can_collapse_block_end_margin_with_kids = margins_may_collapse == MarginsMayCollapseFlag::MarginsMayCollapse && !self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) && self.fragment.border_padding.block_end == Au(0); let (collapsible_margins, delta) = margin_collapse_info.finish_and_compute_collapsible_margins( &self.fragment, self.base.block_container_explicit_block_size, can_collapse_block_end_margin_with_kids); self.base.collapsible_margins = collapsible_margins; translate_including_floats(&mut cur_b, delta, &mut floats); // FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page, but // this is not correct behavior according to CSS 2.1 § 10.5. Instead I think we should // treat the root element as having `overflow: scroll` and use the layers-based // scrolling infrastructure to make it scrollable. let mut block_size = cur_b - block_start_offset; let is_root = self.is_root(); if is_root { let viewport_size = LogicalSize::from_physical(self.fragment.style.writing_mode, layout_context.shared.viewport_size); block_size = max(viewport_size.block, block_size) } if is_root || self.formatting_context_type() != FormattingContextType::None || self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { // The content block-size includes all the floats per CSS 2.1 § 10.6.7. The easiest // way to handle this is to just treat it as clearance. block_size = block_size + floats.clearance(ClearType::Both); } if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { // Store the content block-size for use in calculating the absolute flow's // dimensions later. // // FIXME(pcwalton): This looks not idempotent. Is it? self.fragment.border_box.size.block = block_size; } // Write in the size of the relative containing block for children. (This information // is also needed to handle RTL.) for kid in self.base.child_iter() { flow::mut_base(kid).early_absolute_position_info = EarlyAbsolutePositionInfo { relative_containing_block_size: self.fragment.content_box().size, relative_containing_block_mode: self.fragment.style().writing_mode, }; } if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { return } // Compute any explicitly-specified block size. // Can't use `for` because we assign to `candidate_block_size_iterator.candidate_value`. let mut candidate_block_size_iterator = CandidateBSizeIterator::new( &self.fragment, self.base.block_container_explicit_block_size); while let Some(candidate_block_size) = candidate_block_size_iterator.next() { candidate_block_size_iterator.candidate_value = match candidate_block_size { MaybeAuto::Auto => block_size, MaybeAuto::Specified(value) => value } } // Adjust `cur_b` as necessary to account for the explicitly-specified block-size. block_size = candidate_block_size_iterator.candidate_value; let delta = block_size - (cur_b - block_start_offset); translate_including_floats(&mut cur_b, delta, &mut floats); // Take border and padding into account. let block_end_offset = self.fragment.border_padding.block_end; translate_including_floats(&mut cur_b, block_end_offset, &mut floats); // Now that `cur_b` is at the block-end of the border box, compute the final border box // position. self.fragment.border_box.size.block = cur_b; self.fragment.border_box.start.b = Au(0); if !self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { self.base.position.size.block = cur_b; } // Store the current set of floats in the flow so that flows that come later in the // document can access them. self.base.floats = floats.clone(); self.adjust_fragments_for_collapsed_margins_if_root(); } else { // We don't need to reflow, but we still need to perform in-order traversals if // necessary. let thread_id = self.base.thread_id; for kid in self.base.child_iter() { kid.assign_block_size_for_inorder_child_if_necessary(layout_context, thread_id); } } if (&*self as &Flow).contains_roots_of_absolute_flow_tree() { // Assign block-sizes for all flows in this absolute flow tree. // This is preorder because the block-size of an absolute flow may depend on // the block-size of its containing block, which may also be an absolute flow. (&mut *self as &mut Flow).traverse_preorder_absolute_flows( &mut AbsoluteAssignBSizesTraversal(layout_context)); } // Don't remove the dirty bits yet if we're absolutely-positioned, since our final size // has not been calculated yet. (See `calculate_absolute_block_size_and_margins` for that.) // Also don't remove the dirty bits if we're a block formatting context since our inline // size has not yet been computed. (See `assign_inline_position_for_formatting_context()`.) if (self.base.flags.is_float() || self.formatting_context_type() == FormattingContextType::None) && !self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { self.base.restyle_damage.remove(REFLOW_OUT_OF_FLOW | REFLOW); } } /// Add placement information about current float flow for use by the parent. /// /// Also, use information given by parent about other floats to find out our relative position. /// /// This does not give any information about any float descendants because they do not affect /// elements outside of the subtree rooted at this float. /// /// This function is called on a kid flow by a parent. Therefore, `assign_block_size_float` was /// already called on this kid flow by the traversal function. So, the values used are /// well-defined. pub fn place_float(&mut self) { let block_size = self.fragment.border_box.size.block; let clearance = match self.fragment.clear() { None => Au(0), Some(clear) => self.base.floats.clearance(clear), }; let float_info: FloatedBlockInfo = (**self.float.as_ref().unwrap()).clone(); // Our `position` field accounts for positive margins, but not negative margins. (See // calculation of `extra_inline_size_from_margin` below.) Negative margins must be taken // into account for float placement, however. So we add them in here. let inline_size_for_float_placement = self.base.position.size.inline + min(Au(0), self.fragment.margin.inline_start_end()); let info = PlacementInfo { size: LogicalSize::new( self.fragment.style.writing_mode, inline_size_for_float_placement, block_size + self.fragment.margin.block_start_end()) .convert(self.fragment.style.writing_mode, self.base.floats.writing_mode), ceiling: clearance + float_info.float_ceiling, max_inline_size: float_info.containing_inline_size, kind: float_info.float_kind, }; // Place the float and return the `Floats` back to the parent flow. // After, grab the position and use that to set our position. self.base.floats.add_float(&info); // FIXME (mbrubeck) Get the correct container size for self.base.floats; let container_size = Size2D::new(self.base.block_container_inline_size, Au(0)); // Move in from the margin edge, as per CSS 2.1 § 9.5, floats may not overlap anything on // their margin edges. let float_offset = self.base.floats.last_float_pos().unwrap() .convert(self.base.floats.writing_mode, self.base.writing_mode, container_size) .start; let margin_offset = LogicalPoint::new(self.base.writing_mode, Au(0), self.fragment.margin.block_start); let mut origin = LogicalPoint::new(self.base.writing_mode, self.base.position.start.i, self.base.position.start.b); origin = origin.add_point(&float_offset).add_point(&margin_offset); self.base.position = LogicalRect::from_point_size(self.base.writing_mode, origin, self.base.position.size); } pub fn explicit_block_containing_size(&self, layout_context: &LayoutContext) -> Option { if self.is_root() || self.is_fixed() { let viewport_size = LogicalSize::from_physical(self.fragment.style.writing_mode, layout_context.shared.viewport_size); Some(viewport_size.block) } else if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) && self.base.block_container_explicit_block_size.is_none() { self.base.absolute_cb.explicit_block_containing_size(layout_context) } else { self.base.block_container_explicit_block_size } } fn explicit_block_size(&self, containing_block_size: Option) -> Option { let content_block_size = self.fragment.style().content_block_size(); match (content_block_size, containing_block_size) { (LengthOrPercentageOrAuto::Calc(calc), Some(container_size)) => { Some(container_size.scale_by(calc.percentage()) + calc.length()) } (LengthOrPercentageOrAuto::Length(length), _) => Some(length), (LengthOrPercentageOrAuto::Percentage(percent), Some(container_size)) => { Some(container_size.scale_by(percent)) } (LengthOrPercentageOrAuto::Percentage(_), None) | (LengthOrPercentageOrAuto::Calc(_), None) | (LengthOrPercentageOrAuto::Auto, None) => { None } (LengthOrPercentageOrAuto::Auto, Some(container_size)) => { let (block_start, block_end) = { let position = self.fragment.style().logical_position(); (MaybeAuto::from_style(position.block_start, container_size), MaybeAuto::from_style(position.block_end, container_size)) }; match (block_start, block_end) { (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end)) => { let available_block_size = container_size - self.fragment.border_padding.block_start_end(); // Non-auto margin-block-start and margin-block-end values have already been // calculated during assign-inline-size. let margin = self.fragment.style().logical_margin(); let margin_block_start = match margin.block_start { LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto, _ => MaybeAuto::Specified(self.fragment.margin.block_start) }; let margin_block_end = match margin.block_end { LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto, _ => MaybeAuto::Specified(self.fragment.margin.block_end) }; let margin_block_start = margin_block_start.specified_or_zero(); let margin_block_end = margin_block_end.specified_or_zero(); let sum = block_start + block_end + margin_block_start + margin_block_end; Some(available_block_size - sum) } (_, _) => { None } } } } } fn calculate_absolute_block_size_and_margins(&mut self, layout_context: &LayoutContext) { let opaque_self = OpaqueFlow::from_flow(self); let containing_block_block_size = self.containing_block_size(&layout_context.shared.viewport_size, opaque_self).block; // This is the stored content block-size value from assign-block-size let content_block_size = self.fragment.border_box.size.block; let mut solution = None; { // Non-auto margin-block-start and margin-block-end values have already been // calculated during assign-inline-size. let margin = self.fragment.style().logical_margin(); let margin_block_start = match margin.block_start { LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto, _ => MaybeAuto::Specified(self.fragment.margin.block_start) }; let margin_block_end = match margin.block_end { LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto, _ => MaybeAuto::Specified(self.fragment.margin.block_end) }; let block_start; let block_end; { let position = self.fragment.style().logical_position(); block_start = MaybeAuto::from_style(position.block_start, containing_block_block_size); block_end = MaybeAuto::from_style(position.block_end, containing_block_block_size); } let available_block_size = containing_block_block_size - self.fragment.border_padding.block_start_end(); if self.is_replaced_content() { // Calculate used value of block-size just like we do for inline replaced elements. // TODO: Pass in the containing block block-size when Fragment's // assign-block-size can handle it correctly. self.fragment.assign_replaced_block_size_if_necessary(Some(containing_block_block_size)); // TODO: Right now, this content block-size value includes the // margin because of erroneous block-size calculation in fragment. // Check this when that has been fixed. let block_size_used_val = self.fragment.border_box.size.block; solution = Some(BSizeConstraintSolution::solve_vertical_constraints_abs_replaced( block_size_used_val, margin_block_start, margin_block_end, block_start, block_end, content_block_size, available_block_size)) } else { let mut candidate_block_size_iterator = CandidateBSizeIterator::new(&self.fragment, Some(containing_block_block_size)); // Can't use `for` because we assign to // `candidate_block_size_iterator.candidate_value`. while let Some(block_size_used_val) = candidate_block_size_iterator.next() { solution = Some( BSizeConstraintSolution::solve_vertical_constraints_abs_nonreplaced( block_size_used_val, margin_block_start, margin_block_end, block_start, block_end, content_block_size, available_block_size)); candidate_block_size_iterator.candidate_value = solution.unwrap().block_size; } } } let solution = solution.unwrap(); self.fragment.margin.block_start = solution.margin_block_start; self.fragment.margin.block_end = solution.margin_block_end; self.fragment.border_box.start.b = Au(0); if !self.base.flags.contains(BLOCK_POSITION_IS_STATIC) { self.base.position.start.b = solution.block_start + self.fragment.margin.block_start } let block_size = solution.block_size + self.fragment.border_padding.block_start_end(); self.fragment.border_box.size.block = block_size; self.base.position.size.block = block_size; self.base.restyle_damage.remove(REFLOW_OUT_OF_FLOW | REFLOW); } /// Compute inline size based using the `block_container_inline_size` set by the parent flow. /// /// This is run in the `AssignISizes` traversal. fn propagate_and_compute_used_inline_size(&mut self, layout_context: &LayoutContext) { let containing_block_inline_size = self.base.block_container_inline_size; self.compute_used_inline_size(layout_context, containing_block_inline_size); if self.base.flags.is_float() { self.float.as_mut().unwrap().containing_inline_size = containing_block_inline_size } } /// Assigns the computed inline-start content edge and inline-size to all the children of this /// block flow. Also computes whether each child will be impacted by floats. The given /// `callback`, if supplied, will be called once per child; it is currently used to push down /// column sizes for tables. /// /// `#[inline(always)]` because this is called only from block or table inline-size assignment /// and the code for block layout is significantly simpler. #[inline(always)] pub fn propagate_assigned_inline_size_to_children(&mut self, layout_context: &LayoutContext, inline_start_content_edge: Au, inline_end_content_edge: Au, content_inline_size: Au, mut callback: F) where F: FnMut(&mut Flow, usize, Au, WritingMode, &mut Au, &mut Au) { // Keep track of whether floats could impact each child. let mut inline_start_floats_impact_child = self.base.flags.contains(IMPACTED_BY_LEFT_FLOATS); let mut inline_end_floats_impact_child = self.base.flags.contains(IMPACTED_BY_RIGHT_FLOATS); let flags = self.base.flags.clone(); // Remember the inline-sizes of the last left and right floats, if there were any. These // are used for estimating the inline-sizes of block formatting contexts. (We estimate that // the inline-size of any block formatting context that we see will be based on the // inline-size of the containing block as well as the last float seen before it in each // direction.) let mut inline_size_of_preceding_left_floats = Au(0); let mut inline_size_of_preceding_right_floats = Au(0); if self.formatting_context_type() == FormattingContextType::None { if inline_start_content_edge > Au(0) { inline_size_of_preceding_left_floats = max(self.inline_size_of_preceding_left_floats - inline_start_content_edge, Au(0)); } if inline_end_content_edge > Au(0) { inline_size_of_preceding_right_floats = max(self.inline_size_of_preceding_right_floats - inline_end_content_edge, Au(0)); } } let opaque_self = OpaqueFlow::from_flow(self); // Calculate non-auto block size to pass to children. let parent_container_size = self.explicit_block_containing_size(layout_context); let explicit_content_size = self.explicit_block_size(parent_container_size); // Calculate containing block inline size. let containing_block_size = if flags.contains(IS_ABSOLUTELY_POSITIONED) { self.containing_block_size(&layout_context.shared.viewport_size, opaque_self).inline } else { content_inline_size }; // FIXME (mbrubeck): Get correct mode for absolute containing block let containing_block_mode = self.base.writing_mode; let mut inline_start_margin_edge = inline_start_content_edge; let mut inline_end_margin_edge = inline_end_content_edge; let mut iterator = self.base.child_iter().enumerate().peekable(); while let Some((i, kid)) = iterator.next() { flow::mut_base(kid).block_container_explicit_block_size = explicit_content_size; // Determine float impaction, and update the inline size speculations if necessary. if flow::base(kid).flags.contains(CLEARS_LEFT) { inline_start_floats_impact_child = false; inline_size_of_preceding_left_floats = Au(0); } if flow::base(kid).flags.contains(CLEARS_RIGHT) { inline_end_floats_impact_child = false; inline_size_of_preceding_right_floats = Au(0); } // Update the speculated inline size if this child is floated. match flow::base(kid).flags.float_kind() { float::T::none => {} float::T::left => { inline_size_of_preceding_left_floats = inline_size_of_preceding_left_floats + flow::base(kid).intrinsic_inline_sizes.preferred_inline_size; } float::T::right => { inline_size_of_preceding_right_floats = inline_size_of_preceding_right_floats + flow::base(kid).intrinsic_inline_sizes.preferred_inline_size; } } // The inline-start margin edge of the child flow is at our inline-start content edge, // and its inline-size is our content inline-size. let kid_mode = flow::base(kid).writing_mode; { let kid_base = flow::mut_base(kid); if kid_base.flags.contains(INLINE_POSITION_IS_STATIC) { kid_base.position.start.i = if kid_mode.is_bidi_ltr() == containing_block_mode.is_bidi_ltr() { inline_start_content_edge } else { // The kid's inline 'start' is at the parent's 'end' inline_end_content_edge }; } kid_base.block_container_inline_size = content_inline_size; kid_base.block_container_writing_mode = containing_block_mode; } { let kid_base = flow::mut_base(kid); inline_start_floats_impact_child = inline_start_floats_impact_child || kid_base.flags.contains(HAS_LEFT_FLOATED_DESCENDANTS); inline_end_floats_impact_child = inline_end_floats_impact_child || kid_base.flags.contains(HAS_RIGHT_FLOATED_DESCENDANTS); kid_base.flags.set(IMPACTED_BY_LEFT_FLOATS, inline_start_floats_impact_child); kid_base.flags.set(IMPACTED_BY_RIGHT_FLOATS, inline_end_floats_impact_child); } if kid.is_block_flow() { let kid_block = kid.as_mut_block(); kid_block.inline_size_of_preceding_left_floats = inline_size_of_preceding_left_floats; kid_block.inline_size_of_preceding_right_floats = inline_size_of_preceding_right_floats; } // Call the callback to propagate extra inline size information down to the child. This // is currently used for tables. callback(kid, i, content_inline_size, containing_block_mode, &mut inline_start_margin_edge, &mut inline_end_margin_edge); // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow. // // TODO(#2265, pcwalton): Do this in the cascade instead. let containing_block_text_align = self.fragment.style().get_inheritedtext().text_align; flow::mut_base(kid).flags.set_text_align(containing_block_text_align); // Handle `text-indent` on behalf of any inline children that we have. This is // necessary because any percentages are relative to the containing block, which only // we know. if kid.is_inline_flow() { kid.as_mut_inline().first_line_indentation = specified(self.fragment.style().get_inheritedtext().text_indent, containing_block_size); } } } /// Determines the type of formatting context this is. See the definition of /// `FormattingContextType`. fn formatting_context_type(&self) -> FormattingContextType { let style = self.fragment.style(); if style.get_box().float != float::T::none { return FormattingContextType::Other } match style.get_box().display { display::T::table_cell | display::T::table_caption | display::T::table_row_group | display::T::table | display::T::inline_block => { FormattingContextType::Other } _ if style.get_box().overflow_x != overflow_x::T::visible || style.get_box().overflow_y != overflow_y::T(overflow_x::T::visible) || style.is_multicol() => { FormattingContextType::Block } _ => FormattingContextType::None, } } /// Per CSS 2.1 § 9.5, block formatting contexts' inline widths and positions are affected by /// the presence of floats. This is the part of the assign-heights traversal that computes /// the final inline position and width for such flows. /// /// Note that this is part of the assign-block-sizes traversal, not the assign-inline-sizes /// traversal as one might expect. That is because, in general, float placement cannot occur /// until heights are assigned. To work around this unfortunate circular dependency, by the /// time we get here we have already estimated the width of the block formatting context based /// on the floats we could see at the time of inline-size assignment. The job of this function, /// therefore, is not only to assign the final size but also to perform the layout again for /// this block formatting context if our speculation was wrong. /// /// FIXME(pcwalton): This code is not incremental-reflow-safe (i.e. not idempotent). fn assign_inline_position_for_formatting_context(&mut self) { debug_assert!(self.formatting_context_type() != FormattingContextType::None); if !self.base.restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW) { return } let info = PlacementInfo { size: self.fragment.border_box.size.convert(self.fragment.style.writing_mode, self.base.floats.writing_mode), ceiling: self.base.position.start.b, max_inline_size: MAX_AU, kind: FloatKind::Left, }; // Offset our position by whatever displacement is needed to not impact the floats. let rect = self.base.floats.place_between_floats(&info); self.base.position.start.i = self.base.position.start.i + rect.start.i; // TODO(pcwalton): If the inline-size of this flow is different from the size we estimated // earlier, lay it out again. self.base.restyle_damage.remove(REFLOW_OUT_OF_FLOW | REFLOW); } fn is_inline_block(&self) -> bool { self.fragment.style().get_box().display == display::T::inline_block } /// Computes the content portion (only) of the intrinsic inline sizes of this flow. This is /// used for calculating shrink-to-fit width. Assumes that intrinsic sizes have already been /// computed for this flow. fn content_intrinsic_inline_sizes(&self) -> IntrinsicISizes { let surrounding_inline_size = self.fragment.surrounding_intrinsic_inline_size(); IntrinsicISizes { minimum_inline_size: self.base.intrinsic_inline_sizes.minimum_inline_size - surrounding_inline_size, preferred_inline_size: self.base.intrinsic_inline_sizes.preferred_inline_size - surrounding_inline_size, } } /// Computes intrinsic widths for a block. pub fn bubble_inline_sizes_for_block(&mut self, consult_children: bool) { let _scope = layout_debug_scope!("block::bubble_inline_sizes {:x}", self.base.debug_id()); let mut flags = self.base.flags; flags.remove(HAS_LEFT_FLOATED_DESCENDANTS); flags.remove(HAS_RIGHT_FLOATED_DESCENDANTS); // Find the maximum inline-size from children. let mut computation = self.fragment.compute_intrinsic_inline_sizes(); let (mut left_float_width, mut right_float_width) = (Au(0), Au(0)); let (mut left_float_width_accumulator, mut right_float_width_accumulator) = (Au(0), Au(0)); for kid in self.base.child_iter() { let is_absolutely_positioned = flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED); let child_base = flow::mut_base(kid); let float_kind = child_base.flags.float_kind(); if !is_absolutely_positioned && consult_children { computation.content_intrinsic_sizes.minimum_inline_size = max(computation.content_intrinsic_sizes.minimum_inline_size, child_base.intrinsic_inline_sizes.minimum_inline_size); if child_base.flags.contains(CLEARS_LEFT) { left_float_width = max(left_float_width, left_float_width_accumulator); left_float_width_accumulator = Au(0) } if child_base.flags.contains(CLEARS_RIGHT) { right_float_width = max(right_float_width, right_float_width_accumulator); right_float_width_accumulator = Au(0) } match float_kind { float::T::none => { computation.content_intrinsic_sizes.preferred_inline_size = max(computation.content_intrinsic_sizes.preferred_inline_size, child_base.intrinsic_inline_sizes.preferred_inline_size); } float::T::left => { left_float_width_accumulator = left_float_width_accumulator + child_base.intrinsic_inline_sizes.preferred_inline_size; } float::T::right => { right_float_width_accumulator = right_float_width_accumulator + child_base.intrinsic_inline_sizes.preferred_inline_size; } } } flags.union_floated_descendants_flags(child_base.flags); } // FIXME(pcwalton): This should consider all float descendants, not just children. // FIXME(pcwalton): This is not well-spec'd; INTRINSIC specifies to do this, but CSS-SIZING // says not to. In practice, Gecko and WebKit both do this. left_float_width = max(left_float_width, left_float_width_accumulator); right_float_width = max(right_float_width, right_float_width_accumulator); computation.content_intrinsic_sizes.preferred_inline_size = max(computation.content_intrinsic_sizes.preferred_inline_size, left_float_width + right_float_width); self.base.intrinsic_inline_sizes = computation.finish(); match self.fragment.style().get_box().float { float::T::none => {} float::T::left => flags.insert(HAS_LEFT_FLOATED_DESCENDANTS), float::T::right => flags.insert(HAS_RIGHT_FLOATED_DESCENDANTS), } self.base.flags = flags } fn determine_if_layer_needed(&mut self) { if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { // Fixed position layers get layers. if self.is_fixed() { self.base.flags.insert(NEEDS_LAYER); return } } // This flow needs a layer if it has a 3d transform, or provides perspective // to child layers. See http://dev.w3.org/csswg/css-transforms/#3d-rendering-contexts. let has_3d_transform = self.fragment.style().transform_requires_layer(); let has_perspective = self.fragment.style().get_effects().perspective != LengthOrNone::None; if has_3d_transform || has_perspective { self.base.flags.insert(NEEDS_LAYER); return } match (self.fragment.style().get_box().overflow_x, self.fragment.style().get_box().overflow_y.0) { (overflow_x::T::auto, _) | (overflow_x::T::scroll, _) | (_, overflow_x::T::auto) | (_, overflow_x::T::scroll) => { self.base.flags.insert(NEEDS_LAYER); } _ => {} } } } impl Flow for BlockFlow { fn class(&self) -> FlowClass { FlowClass::Block } fn as_mut_block(&mut self) -> &mut BlockFlow { self } fn as_block(&self) -> &BlockFlow { self } /// Pass 1 of reflow: computes minimum and preferred inline-sizes. /// /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When /// called on this flow, all child flows have had their minimum and preferred inline-sizes set. /// This function must decide minimum/preferred inline-sizes based on its children's /// inline-sizes and the dimensions of any fragments it is responsible for flowing. fn bubble_inline_sizes(&mut self) { // If this block has a fixed width, just use that for the minimum and preferred width, // rather than bubbling up children inline width. let consult_children = match self.fragment.style().get_box().width { LengthOrPercentageOrAuto::Length(_) => false, _ => true, }; self.bubble_inline_sizes_for_block(consult_children) } /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. /// When called on this context, the context has had its inline-size set by the parent context. /// /// Dual fragments consume some inline-size first, and the remainder is assigned to all child /// (block) contexts. fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { let _scope = layout_debug_scope!("block::assign_inline_sizes {:x}", self.base.debug_id()); if !self.base.restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW) { return } debug!("assign_inline_sizes({}): assigning inline_size for flow", if self.base.flags.is_float() { "float" } else { "block" }); self.base.floats = Floats::new(self.base.writing_mode); if self.is_root() { debug!("Setting root position"); self.base.position.start = LogicalPoint::zero(self.base.writing_mode); self.base.block_container_inline_size = LogicalSize::from_physical( self.base.writing_mode, layout_context.shared.viewport_size).inline; self.base.block_container_writing_mode = self.base.writing_mode; // The root element is never impacted by floats. self.base.flags.remove(IMPACTED_BY_LEFT_FLOATS); self.base.flags.remove(IMPACTED_BY_RIGHT_FLOATS); } // Our inline-size was set to the inline-size of the containing block by the flow's parent. // Now compute the real value. self.propagate_and_compute_used_inline_size(layout_context); // Formatting contexts are never impacted by floats. match self.formatting_context_type() { FormattingContextType::None => {} FormattingContextType::Block => { self.base.flags.remove(IMPACTED_BY_LEFT_FLOATS); self.base.flags.remove(IMPACTED_BY_RIGHT_FLOATS); // We can't actually compute the inline-size of this block now, because floats // might affect it. Speculate that its inline-size is equal to the inline-size // computed above minus the inline-size of the previous left and/or right floats. // // (If `max-width` is set, then don't perform this speculation. We guess that the // page set `max-width` in order to avoid hitting floats. The search box on Google // SERPs falls into this category.) if self.fragment.style.max_inline_size() == LengthOrPercentageOrNone::None { self.fragment.border_box.size.inline = self.fragment.border_box.size.inline - self.inline_size_of_preceding_left_floats - self.inline_size_of_preceding_right_floats; } } FormattingContextType::Other => { self.base.flags.remove(IMPACTED_BY_LEFT_FLOATS); self.base.flags.remove(IMPACTED_BY_RIGHT_FLOATS); } } // Move in from the inline-start border edge. let inline_start_content_edge = self.fragment.border_box.start.i + self.fragment.border_padding.inline_start; let padding_and_borders = self.fragment.border_padding.inline_start_end(); // Distance from the inline-end margin edge to the inline-end content edge. let inline_end_content_edge = self.fragment.margin.inline_end + self.fragment.border_padding.inline_end; let content_inline_size = self.fragment.border_box.size.inline - padding_and_borders; self.propagate_assigned_inline_size_to_children(layout_context, inline_start_content_edge, inline_end_content_edge, content_inline_size, |_, _, _, _, _, _| {}); } fn place_float_if_applicable<'a>(&mut self, _: &'a LayoutContext<'a>) { if self.base.flags.is_float() { self.place_float(); } } fn assign_block_size_for_inorder_child_if_necessary<'a>(&mut self, layout_context: &'a LayoutContext<'a>, parent_thread_id: u8) -> bool { if self.base.flags.is_float() { return false } let is_formatting_context = self.formatting_context_type() != FormattingContextType::None; if !self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) && is_formatting_context { self.assign_inline_position_for_formatting_context(); } if self.base.flags.impacted_by_floats() { self.base.thread_id = parent_thread_id; if self.base.restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW) { self.assign_block_size(layout_context); // Don't remove the restyle damage; `assign_block_size` decides whether that is // appropriate (which in the case of e.g. absolutely-positioned flows, it is not). } return true } if is_formatting_context { // If this is a formatting context and was *not* impacted by floats, then we must // translate the floats past us. let writing_mode = self.base.floats.writing_mode; let delta = self.base.position.size.block; self.base.floats.translate(LogicalSize::new(writing_mode, Au(0), -delta)); return true } false } fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { if self.is_replaced_content() { let _scope = layout_debug_scope!("assign_replaced_block_size_if_necessary {:x}", self.base.debug_id()); // Assign block-size for fragment if it is an image fragment. let containing_block_block_size = self.base.block_container_explicit_block_size; self.fragment.assign_replaced_block_size_if_necessary(containing_block_block_size); if !self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { self.base.position.size.block = self.fragment.border_box.size.block; } } else if self.is_root() || self.base.flags.is_float() || self.is_inline_block() { // Root element margins should never be collapsed according to CSS § 8.3.1. debug!("assign_block_size: assigning block_size for root flow {:?}", flow::base(self).debug_id()); self.assign_block_size_block_base(ctx, MarginsMayCollapseFlag::MarginsMayNotCollapse); } else { debug!("assign_block_size: assigning block_size for block {:?}", flow::base(self).debug_id()); self.assign_block_size_block_base(ctx, MarginsMayCollapseFlag::MarginsMayCollapse); } } fn compute_absolute_position(&mut self, layout_context: &LayoutContext) { if self.base.flags.contains(NEEDS_LAYER) { self.fragment.flags.insert(HAS_LAYER) } // FIXME (mbrubeck): Get the real container size, taking the container writing mode into // account. Must handle vertical writing modes. let container_size = Size2D::new(self.base.block_container_inline_size, Au(0)); if self.is_root() { self.base.clip = ClippingRegion::max(); self.base.stacking_relative_position_of_display_port = MAX_RECT; } let transform_style = self.fragment.style().get_used_transform_style(); if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { // `overflow: auto` and `overflow: scroll` force creation of layers, since we can only // scroll layers. match (self.fragment.style().get_box().overflow_x, self.fragment.style().get_box().overflow_y.0) { (overflow_x::T::auto, _) | (overflow_x::T::scroll, _) | (_, overflow_x::T::auto) | (_, overflow_x::T::scroll) => { self.base.clip = ClippingRegion::max(); self.base.stacking_relative_position_of_display_port = MAX_RECT; } _ => {} } let position_start = self.base.position.start.to_physical(self.base.writing_mode, container_size); // Compute our position relative to the nearest ancestor stacking context. This will be // passed down later as part of containing block details for absolute descendants. let absolute_stacking_relative_position = if self.is_fixed() { // The viewport is initially at (0, 0). position_start } else { // Absolute position of the containing block + position of absolute // flow w.r.t. the containing block. self.base .late_absolute_position_info .stacking_relative_position_of_absolute_containing_block + position_start }; if !self.base.writing_mode.is_vertical() { if !self.base.flags.contains(INLINE_POSITION_IS_STATIC) { self.base.stacking_relative_position.x = absolute_stacking_relative_position.x } if !self.base.flags.contains(BLOCK_POSITION_IS_STATIC) { self.base.stacking_relative_position.y = absolute_stacking_relative_position.y } } else { if !self.base.flags.contains(INLINE_POSITION_IS_STATIC) { self.base.stacking_relative_position.y = absolute_stacking_relative_position.y } if !self.base.flags.contains(BLOCK_POSITION_IS_STATIC) { self.base.stacking_relative_position.x = absolute_stacking_relative_position.x } } } // For relatively-positioned descendants, the containing block formed by a block is just // the content box. The containing block for absolutely-positioned descendants, on the // other hand, is only established if we are positioned. let relative_offset = self.fragment.relative_position(&self.base .early_absolute_position_info .relative_containing_block_size); if self.contains_positioned_fragments() { let border_box_origin = (self.fragment.border_box - self.fragment.style.logical_border_width()).start; self.base .late_absolute_position_info .stacking_relative_position_of_absolute_containing_block = self.base.stacking_relative_position + (border_box_origin + relative_offset).to_physical(self.base.writing_mode, container_size) } // Compute absolute position info for children. let stacking_relative_position_of_absolute_containing_block_for_children = if self.fragment.establishes_stacking_context() { let logical_border_width = self.fragment.style().logical_border_width(); let position = LogicalPoint::new(self.base.writing_mode, logical_border_width.inline_start, logical_border_width.block_start); let position = position.to_physical(self.base.writing_mode, container_size); if self.contains_positioned_fragments() { position } else { // We establish a stacking context but are not positioned. (This will happen // if, for example, the element has `position: static` but has `opacity` or // `transform` set.) In this case, absolutely-positioned children will not be // positioned relative to us but will instead be positioned relative to our // containing block. position - self.base.stacking_relative_position } } else { self.base .late_absolute_position_info .stacking_relative_position_of_absolute_containing_block }; let late_absolute_position_info_for_children = LateAbsolutePositionInfo { stacking_relative_position_of_absolute_containing_block: stacking_relative_position_of_absolute_containing_block_for_children, }; let container_size_for_children = self.base.position.size.to_physical(self.base.writing_mode); // Compute the origin and clipping rectangle for children. let origin_for_children; let clip_in_child_coordinate_system; let is_stacking_context = self.fragment.establishes_stacking_context(); if is_stacking_context { // We establish a stacking context, so the position of our children is vertically // correct, but has to be adjusted to accommodate horizontal margins. (Note the // calculation involving `position` below and recall that inline-direction flow // positions are relative to the edges of the margin box.) // // FIXME(pcwalton): Is this vertical-writing-direction-safe? let margin = self.fragment.margin.to_physical(self.base.writing_mode); origin_for_children = Point2D::new(-margin.left, Au(0)); clip_in_child_coordinate_system = self.base.clip.translate(&-self.base.stacking_relative_position); } else { let relative_offset = relative_offset.to_physical(self.base.writing_mode); origin_for_children = self.base.stacking_relative_position + relative_offset; clip_in_child_coordinate_system = self.base.clip.clone(); } let stacking_relative_position_of_display_port_for_children = if is_stacking_context || self.is_root() { let visible_rect = match layout_context.shared.visible_rects.get(&self.layer_id()) { Some(visible_rect) => *visible_rect, None => Rect::new(Point2D::zero(), layout_context.shared.viewport_size), }; let viewport_size = layout_context.shared.viewport_size; visible_rect.inflate(viewport_size.width * DISPLAY_PORT_SIZE_FACTOR, viewport_size.height * DISPLAY_PORT_SIZE_FACTOR) } else if is_stacking_context { self.base .stacking_relative_position_of_display_port .translate(&-self.base.stacking_relative_position) } else { self.base.stacking_relative_position_of_display_port }; let stacking_relative_border_box = self.fragment .stacking_relative_border_box(&self.base.stacking_relative_position, &self.base .early_absolute_position_info .relative_containing_block_size, self.base .early_absolute_position_info .relative_containing_block_mode, CoordinateSystem::Own); let clip = self.fragment.clipping_region_for_children( &clip_in_child_coordinate_system, &stacking_relative_border_box, self.base.flags.contains(IS_ABSOLUTELY_POSITIONED)); // Process children. for kid in self.base.child_iter() { // If this layer preserves the 3d context of children, // then children will need a render layer. // TODO(gw): This isn't always correct. In some cases // this may create extra layers than needed. I think // there are also some edge cases where children don't // get a layer when they should. if transform_style == transform_style::T::preserve_3d { flow::mut_base(kid).flags.insert(NEEDS_LAYER); } if flow::base(kid).flags.contains(INLINE_POSITION_IS_STATIC) || flow::base(kid).flags.contains(BLOCK_POSITION_IS_STATIC) { let kid_base = flow::mut_base(kid); let physical_position = kid_base.position.to_physical(kid_base.writing_mode, container_size_for_children); // Set the inline and block positions as necessary. if !kid_base.writing_mode.is_vertical() { if kid_base.flags.contains(INLINE_POSITION_IS_STATIC) { kid_base.stacking_relative_position.x = origin_for_children.x + physical_position.origin.x } if kid_base.flags.contains(BLOCK_POSITION_IS_STATIC) { kid_base.stacking_relative_position.y = origin_for_children.y + physical_position.origin.y } } else { if kid_base.flags.contains(INLINE_POSITION_IS_STATIC) { kid_base.stacking_relative_position.y = origin_for_children.y + physical_position.origin.y } if kid_base.flags.contains(BLOCK_POSITION_IS_STATIC) { kid_base.stacking_relative_position.x = origin_for_children.x + physical_position.origin.x } } } flow::mut_base(kid).late_absolute_position_info = late_absolute_position_info_for_children; flow::mut_base(kid).clip = clip.clone(); flow::mut_base(kid).stacking_relative_position_of_display_port = stacking_relative_position_of_display_port_for_children; } } fn mark_as_root(&mut self) { self.flags.insert(IS_ROOT) } fn is_root(&self) -> bool { self.flags.contains(IS_ROOT) } /// The 'position' property of this flow. fn positioning(&self) -> position::T { self.fragment.style.get_box().position } /// Return the dimensions of the containing block generated by this flow for absolutely- /// positioned descendants. For block flows, this is the padding box. fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize { (self.fragment.border_box - self.fragment.style().logical_border_width()).size } fn layer_id(&self) -> LayerId { self.fragment.layer_id() } fn layer_id_for_overflow_scroll(&self) -> LayerId { self.fragment.layer_id_for_overflow_scroll() } fn is_absolute_containing_block(&self) -> bool { self.contains_positioned_fragments() } fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) && self.fragment.style().logical_position().inline_start == LengthOrPercentageOrAuto::Auto && self.fragment.style().logical_position().inline_end == LengthOrPercentageOrAuto::Auto { self.base.position.start.i = inline_position } } fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) && self.fragment.style().logical_position().block_start == LengthOrPercentageOrAuto::Auto && self.fragment.style().logical_position().block_end == LengthOrPercentageOrAuto::Auto { self.base.position.start.b = block_position } } fn build_display_list(&mut self, layout_context: &LayoutContext) { self.build_display_list_for_block(box DisplayList::new(), layout_context, BorderPaintingMode::Separate); if opts::get().validate_display_list_geometry { self.base.validate_display_list_geometry(); } } fn repair_style(&mut self, new_style: &Arc) { self.fragment.repair_style(new_style) } fn compute_overflow(&self) -> Rect { let flow_size = self.base.position.size.to_physical(self.base.writing_mode); self.fragment.compute_overflow(&flow_size, &self.base .early_absolute_position_info .relative_containing_block_size) } fn iterate_through_fragment_border_boxes(&self, iterator: &mut FragmentBorderBoxIterator, level: i32, stacking_context_position: &Point2D) { if !iterator.should_process(&self.fragment) { return } iterator.process(&self.fragment, level, &self.fragment .stacking_relative_border_box(&self.base.stacking_relative_position, &self.base .early_absolute_position_info .relative_containing_block_size, self.base .early_absolute_position_info .relative_containing_block_mode, CoordinateSystem::Own) .translate(stacking_context_position)); } fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) { (*mutator)(&mut self.fragment) } fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { print_tree.add_item(format!("↑↑ Fragment for block: {:?}", self.fragment)); } } impl fmt::Debug for BlockFlow { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}({:x}) {:?}", self.class(), self.base.debug_id(), self.base) } } /// The inputs for the inline-sizes-and-margins constraint equation. #[derive(Debug, Copy, Clone)] pub struct ISizeConstraintInput { pub computed_inline_size: MaybeAuto, pub inline_start_margin: MaybeAuto, pub inline_end_margin: MaybeAuto, pub inline_start: MaybeAuto, pub inline_end: MaybeAuto, pub text_align: text_align::T, pub available_inline_size: Au, } impl ISizeConstraintInput { pub fn new(computed_inline_size: MaybeAuto, inline_start_margin: MaybeAuto, inline_end_margin: MaybeAuto, inline_start: MaybeAuto, inline_end: MaybeAuto, text_align: text_align::T, available_inline_size: Au) -> ISizeConstraintInput { ISizeConstraintInput { computed_inline_size: computed_inline_size, inline_start_margin: inline_start_margin, inline_end_margin: inline_end_margin, inline_start: inline_start, inline_end: inline_end, text_align: text_align, available_inline_size: available_inline_size, } } } /// The solutions for the inline-size-and-margins constraint equation. #[derive(Copy, Clone, Debug)] pub struct ISizeConstraintSolution { pub inline_start: Au, pub inline_size: Au, pub margin_inline_start: Au, pub margin_inline_end: Au } impl ISizeConstraintSolution { pub fn new(inline_size: Au, margin_inline_start: Au, margin_inline_end: Au) -> ISizeConstraintSolution { ISizeConstraintSolution { inline_start: Au(0), inline_size: inline_size, margin_inline_start: margin_inline_start, margin_inline_end: margin_inline_end, } } fn for_absolute_flow(inline_start: Au, inline_size: Au, margin_inline_start: Au, margin_inline_end: Au) -> ISizeConstraintSolution { ISizeConstraintSolution { inline_start: inline_start, inline_size: inline_size, margin_inline_start: margin_inline_start, margin_inline_end: margin_inline_end, } } } // Trait to encapsulate the ISize and Margin calculation. // // CSS Section 10.3 pub trait ISizeAndMarginsComputer { /// Instructs the fragment to compute its border and padding. fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) { block.fragment.compute_border_and_padding(containing_block_inline_size, border_collapse::T::separate); } /// Compute the inputs for the ISize constraint equation. /// /// This is called only once to compute the initial inputs. For calculations involving /// minimum and maximum inline-size, we don't need to recompute these. fn compute_inline_size_constraint_inputs(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, layout_context: &LayoutContext) -> ISizeConstraintInput { let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, layout_context); block.fragment.compute_block_direction_margins(containing_block_inline_size); block.fragment.compute_inline_direction_margins(containing_block_inline_size); self.compute_border_and_padding(block, containing_block_inline_size); let mut computed_inline_size = self.initial_computed_inline_size(block, parent_flow_inline_size, layout_context); let style = block.fragment.style(); match (computed_inline_size, style.get_box().box_sizing) { (MaybeAuto::Specified(size), box_sizing::T::border_box) => { computed_inline_size = MaybeAuto::Specified(size - block.fragment.border_padding.inline_start_end()) } (MaybeAuto::Auto, box_sizing::T::border_box) | (_, box_sizing::T::content_box) => {} } let margin = style.logical_margin(); let position = style.logical_position(); let available_inline_size = containing_block_inline_size - block.fragment.border_padding.inline_start_end(); ISizeConstraintInput::new(computed_inline_size, MaybeAuto::from_style(margin.inline_start, containing_block_inline_size), MaybeAuto::from_style(margin.inline_end, containing_block_inline_size), MaybeAuto::from_style(position.inline_start, containing_block_inline_size), MaybeAuto::from_style(position.inline_end, containing_block_inline_size), style.get_inheritedtext().text_align, available_inline_size) } /// Set the used values for inline-size and margins from the relevant constraint equation. /// This is called only once. /// /// Set: /// * Used values for content inline-size, inline-start margin, and inline-end margin for this /// flow's box; /// * Inline-start coordinate of this flow's box; /// * Inline-start coordinate of the flow with respect to its containing block (if this is an /// absolute flow). fn set_inline_size_constraint_solutions(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution) { let inline_size; let extra_inline_size_from_margin; { let block_mode = block.base.writing_mode; // FIXME (mbrubeck): Get correct containing block for positioned blocks? let container_mode = block.base.block_container_writing_mode; let container_size = block.base.block_container_inline_size; let fragment = block.fragment(); fragment.margin.inline_start = solution.margin_inline_start; fragment.margin.inline_end = solution.margin_inline_end; // The associated fragment has the border box of this flow. inline_size = solution.inline_size + fragment.border_padding.inline_start_end(); fragment.border_box.size.inline = inline_size; // Start border edge. // FIXME (mbrubeck): Handle vertical writing modes. fragment.border_box.start.i = if container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr() { fragment.margin.inline_start } else { // The parent's "start" direction is the child's "end" direction. container_size - inline_size - fragment.margin.inline_end }; // To calculate the total size of this block, we also need to account for any // additional size contribution from positive margins. Negative margins means the block // isn't made larger at all by the margin. extra_inline_size_from_margin = max(Au(0), fragment.margin.inline_start) + max(Au(0), fragment.margin.inline_end); } // We also resize the block itself, to ensure that overflow is not calculated // as the inline-size of our parent. We might be smaller and we might be larger if we // overflow. flow::mut_base(block).position.size.inline = inline_size + extra_inline_size_from_margin; } /// Set the inline coordinate of the given flow if it is absolutely positioned. fn set_inline_position_of_flow_if_necessary(&self, _: &mut BlockFlow, _: ISizeConstraintSolution) {} /// Solve the inline-size and margins constraints for this block flow. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution; fn initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, layout_context: &LayoutContext) -> MaybeAuto { MaybeAuto::from_style(block.fragment().style().content_inline_size(), self.containing_block_inline_size(block, parent_flow_inline_size, layout_context)) } fn containing_block_inline_size(&self, _: &mut BlockFlow, parent_flow_inline_size: Au, _: &LayoutContext) -> Au { parent_flow_inline_size } /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size. /// /// CSS Section 10.4: Minimum and Maximum inline-sizes fn compute_used_inline_size(&self, block: &mut BlockFlow, layout_context: &LayoutContext, parent_flow_inline_size: Au) { let mut input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, layout_context); let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, layout_context); let mut solution = self.solve_inline_size_constraints(block, &input); // If the tentative used inline-size is greater than 'max-inline-size', inline-size should // be recalculated, but this time using the computed value of 'max-inline-size' as the // computed value for 'inline-size'. match specified_or_none(block.fragment().style().max_inline_size(), containing_block_inline_size) { Some(max_inline_size) if max_inline_size < solution.inline_size => { input.computed_inline_size = MaybeAuto::Specified(max_inline_size); solution = self.solve_inline_size_constraints(block, &input); } _ => {} } // If the resulting inline-size is smaller than 'min-inline-size', inline-size should be // recalculated, but this time using the value of 'min-inline-size' as the computed value // for 'inline-size'. let computed_min_inline_size = specified(block.fragment().style().min_inline_size(), containing_block_inline_size); if computed_min_inline_size > solution.inline_size { input.computed_inline_size = MaybeAuto::Specified(computed_min_inline_size); solution = self.solve_inline_size_constraints(block, &input); } self.set_inline_size_constraint_solutions(block, solution); self.set_inline_position_of_flow_if_necessary(block, solution); } /// Computes inline-start and inline-end margins and inline-size. /// /// This is used by both replaced and non-replaced Blocks. /// /// CSS 2.1 Section 10.3.3. /// Constraint Equation: margin-inline-start + margin-inline-end + inline-size = /// available_inline-size /// where available_inline-size = CB inline-size - (horizontal border + padding) fn solve_block_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size, input.inline_start_margin, input.inline_end_margin, input.available_inline_size); // Check for direction of parent flow (NOT Containing Block) let block_mode = block.base.writing_mode; let container_mode = block.base.block_container_writing_mode; let block_align = block.base.flags.text_align(); // FIXME (mbrubeck): Handle vertical writing modes. let parent_has_same_direction = container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr(); // If inline-size is not 'auto', and inline-size + margins > available_inline-size, all // 'auto' margins are treated as 0. let (inline_start_margin, inline_end_margin) = match computed_inline_size { MaybeAuto::Auto => (inline_start_margin, inline_end_margin), MaybeAuto::Specified(inline_size) => { let inline_start = inline_start_margin.specified_or_zero(); let inline_end = inline_end_margin.specified_or_zero(); if (inline_start + inline_end + inline_size) > available_inline_size { (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end)) } else { (inline_start_margin, inline_end_margin) } } }; // Invariant: inline-start_margin + inline-size + inline-end_margin == // available_inline-size let (inline_start_margin, inline_size, inline_end_margin) = match (inline_start_margin, computed_inline_size, inline_end_margin) { // If all have a computed value other than 'auto', the system is over-constrained. (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(inline_size), MaybeAuto::Specified(margin_end)) => { // servo_left, servo_right, and servo_center are used to implement // the "align descendants" rule in HTML5 § 14.2. if block_align == text_align::T::servo_center { // Ignore any existing margins, and make the inline-start and // inline-end margins equal. let margin = (available_inline_size - inline_size).scale_by(0.5); (margin, inline_size, margin) } else { let ignore_end_margin = match block_align { text_align::T::servo_left => block_mode.is_bidi_ltr(), text_align::T::servo_right => !block_mode.is_bidi_ltr(), _ => parent_has_same_direction, }; if ignore_end_margin { (margin_start, inline_size, available_inline_size - (margin_start + inline_size)) } else { (available_inline_size - (margin_end + inline_size), inline_size, margin_end) } } } // If exactly one value is 'auto', solve for it (MaybeAuto::Auto, MaybeAuto::Specified(inline_size), MaybeAuto::Specified(margin_end)) => (available_inline_size - (inline_size + margin_end), inline_size, margin_end), (MaybeAuto::Specified(margin_start), MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => { (margin_start, available_inline_size - (margin_start + margin_end), margin_end) } (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(inline_size), MaybeAuto::Auto) => { (margin_start, inline_size, available_inline_size - (margin_start + inline_size)) } // If inline-size is set to 'auto', any other 'auto' value becomes '0', // and inline-size is solved for (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => { (Au(0), available_inline_size - margin_end, margin_end) } (MaybeAuto::Specified(margin_start), MaybeAuto::Auto, MaybeAuto::Auto) => { (margin_start, available_inline_size - margin_start, Au(0)) } (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { (Au(0), available_inline_size, Au(0)) } // If inline-start and inline-end margins are auto, they become equal (MaybeAuto::Auto, MaybeAuto::Specified(inline_size), MaybeAuto::Auto) => { let margin = (available_inline_size - inline_size).scale_by(0.5); (margin, inline_size, margin) } }; ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin) } } /// The different types of Blocks. /// /// They mainly differ in the way inline-size and block-sizes and margins are calculated /// for them. pub struct AbsoluteNonReplaced; pub struct AbsoluteReplaced; pub struct BlockNonReplaced; pub struct BlockReplaced; pub struct FloatNonReplaced; pub struct FloatReplaced; pub struct InlineBlockNonReplaced; pub struct InlineBlockReplaced; impl ISizeAndMarginsComputer for AbsoluteNonReplaced { /// Solve the horizontal constraint equation for absolute non-replaced elements. /// /// CSS Section 10.3.7 /// Constraint equation: /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end /// = absolute containing block inline-size - (horizontal padding and border) /// [aka available inline-size] /// /// Return the solution for the equation. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let &ISizeConstraintInput { computed_inline_size, inline_start_margin, inline_end_margin, inline_start, inline_end, available_inline_size, .. } = input; // Check for direction of parent flow (NOT Containing Block) let block_mode = block.base.writing_mode; let container_mode = block.base.block_container_writing_mode; // FIXME (mbrubeck): Handle vertical writing modes. let parent_has_same_direction = container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr(); let (inline_start, inline_size, margin_inline_start, margin_inline_end) = match (inline_start, inline_end, computed_inline_size) { (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); // Now it is the same situation as inline-start Specified and inline-end // and inline-size Auto. // Set inline-end to zero to calculate inline-size. let inline_size = block.get_shrink_to_fit_inline_size(available_inline_size - (margin_start + margin_end)); (Au(0), inline_size, margin_start, margin_end) } (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end), MaybeAuto::Specified(inline_size)) => { match (inline_start_margin, inline_end_margin) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let total_margin_val = available_inline_size - inline_start - inline_end - inline_size; if total_margin_val < Au(0) { if parent_has_same_direction { // margin-inline-start becomes 0 (inline_start, inline_size, Au(0), total_margin_val) } else { // margin-inline-end becomes 0, because it's toward the parent's // inline-start edge. (inline_start, inline_size, total_margin_val, Au(0)) } } else { // Equal margins (inline_start, inline_size, total_margin_val.scale_by(0.5), total_margin_val.scale_by(0.5)) } } (MaybeAuto::Specified(margin_start), MaybeAuto::Auto) => { let sum = inline_start + inline_end + inline_size + margin_start; (inline_start, inline_size, margin_start, available_inline_size - sum) } (MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => { let sum = inline_start + inline_end + inline_size + margin_end; (inline_start, inline_size, available_inline_size - sum, margin_end) } (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(margin_end)) => { // Values are over-constrained. let sum = inline_start + inline_size + margin_start + margin_end; if parent_has_same_direction { // Ignore value for 'inline-end' (inline_start, inline_size, margin_start, margin_end) } else { // Ignore value for 'inline-start' (available_inline_size - sum, inline_size, margin_start, margin_end) } } } } // For the rest of the cases, auto values for margin are set to 0 // If only one is Auto, solve for it (MaybeAuto::Auto, MaybeAuto::Specified(inline_end), MaybeAuto::Specified(inline_size)) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); let sum = inline_end + inline_size + margin_start + margin_end; (available_inline_size - sum, inline_size, margin_start, margin_end) } (MaybeAuto::Specified(inline_start), MaybeAuto::Auto, MaybeAuto::Specified(inline_size)) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); (inline_start, inline_size, margin_start, margin_end) } (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end), MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); let sum = inline_start + inline_end + margin_start + margin_end; (inline_start, available_inline_size - sum, margin_start, margin_end) } // If inline-size is auto, then inline-size is shrink-to-fit. Solve for the // non-auto value. (MaybeAuto::Specified(inline_start), MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); // Set inline-end to zero to calculate inline-size let inline_size = block.get_shrink_to_fit_inline_size(available_inline_size - (margin_start + margin_end)); (inline_start, inline_size, margin_start, margin_end) } (MaybeAuto::Auto, MaybeAuto::Specified(inline_end), MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); // Set inline-start to zero to calculate inline-size let inline_size = block.get_shrink_to_fit_inline_size(available_inline_size - (margin_start + margin_end)); let sum = inline_end + inline_size + margin_start + margin_end; (available_inline_size - sum, inline_size, margin_start, margin_end) } (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(inline_size)) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); // Setting 'inline-start' to static position because direction is 'ltr'. // TODO: Handle 'rtl' when it is implemented. (Au(0), inline_size, margin_start, margin_end) } }; ISizeConstraintSolution::for_absolute_flow(inline_start, inline_size, margin_inline_start, margin_inline_end) } fn containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, layout_context: &LayoutContext) -> Au { let opaque_block = OpaqueFlow::from_flow(block); block.containing_block_size(&layout_context.shared.viewport_size, opaque_block).inline } fn set_inline_position_of_flow_if_necessary(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution) { // Set the inline position of the absolute flow wrt to its containing block. if !block.base.flags.contains(INLINE_POSITION_IS_STATIC) { block.base.position.start.i = solution.inline_start; } } } impl ISizeAndMarginsComputer for AbsoluteReplaced { /// Solve the horizontal constraint equation for absolute replaced elements. /// /// CSS Section 10.3.8 /// Constraint equation: /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end /// = absolute containing block inline-size - (horizontal padding and border) /// [aka available_inline-size] /// /// Return the solution for the equation. fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let &ISizeConstraintInput { computed_inline_size, inline_start_margin, inline_end_margin, inline_start, inline_end, available_inline_size, .. } = input; // TODO: Check for direction of static-position Containing Block (aka // parent flow, _not_ the actual Containing Block) when right-to-left // is implemented // Assume direction is 'ltr' for now // TODO: Handle all the cases for 'rtl' direction. let inline_size = match computed_inline_size { MaybeAuto::Specified(w) => w, _ => panic!("{} {}", "The used value for inline_size for absolute replaced flow", "should have already been calculated by now.") }; let (inline_start, inline_size, margin_inline_start, margin_inline_end) = match (inline_start, inline_end) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); (Au(0), inline_size, margin_start, margin_end) } // If only one is Auto, solve for it (MaybeAuto::Auto, MaybeAuto::Specified(inline_end)) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); let sum = inline_end + inline_size + margin_start + margin_end; (available_inline_size - sum, inline_size, margin_start, margin_end) } (MaybeAuto::Specified(inline_start), MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); (inline_start, inline_size, margin_start, margin_end) } (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end)) => { match (inline_start_margin, inline_end_margin) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let total_margin_val = available_inline_size - inline_start - inline_end - inline_size; if total_margin_val < Au(0) { // margin-inline-start becomes 0 because direction is 'ltr'. (inline_start, inline_size, Au(0), total_margin_val) } else { // Equal margins (inline_start, inline_size, total_margin_val.scale_by(0.5), total_margin_val.scale_by(0.5)) } } (MaybeAuto::Specified(margin_start), MaybeAuto::Auto) => { let sum = inline_start + inline_end + inline_size + margin_start; (inline_start, inline_size, margin_start, available_inline_size - sum) } (MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => { let sum = inline_start + inline_end + inline_size + margin_end; (inline_start, inline_size, available_inline_size - sum, margin_end) } (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(margin_end)) => { // Values are over-constrained. // Ignore value for 'inline-end' cos direction is 'ltr'. (inline_start, inline_size, margin_start, margin_end) } } } }; ISizeConstraintSolution::for_absolute_flow(inline_start, inline_size, margin_inline_start, margin_inline_end) } /// Calculate used value of inline-size just like we do for inline replaced elements. fn initial_computed_inline_size(&self, block: &mut BlockFlow, _: Au, layout_context: &LayoutContext) -> MaybeAuto { let opaque_block = OpaqueFlow::from_flow(block); let containing_block_inline_size = block.containing_block_size(&layout_context.shared.viewport_size, opaque_block).inline; let fragment = block.fragment(); fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size); // For replaced absolute flow, the rest of the constraint solving will // take inline-size to be specified as the value computed here. MaybeAuto::Specified(fragment.content_inline_size()) } fn containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, layout_context: &LayoutContext) -> Au { let opaque_block = OpaqueFlow::from_flow(block); block.containing_block_size(&layout_context.shared.viewport_size, opaque_block).inline } fn set_inline_position_of_flow_if_necessary(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution) { // Set the x-coordinate of the absolute flow wrt to its containing block. block.base.position.start.i = solution.inline_start; } } impl ISizeAndMarginsComputer for BlockNonReplaced { /// Compute inline-start and inline-end margins and inline-size. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { self.solve_block_inline_size_constraints(block, input) } } impl ISizeAndMarginsComputer for BlockReplaced { /// Compute inline-start and inline-end margins and inline-size. /// /// ISize has already been calculated. We now calculate the margins just /// like for non-replaced blocks. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { match input.computed_inline_size { MaybeAuto::Specified(_) => {}, MaybeAuto::Auto => { panic!("BlockReplaced: inline_size should have been computed by now") } }; self.solve_block_inline_size_constraints(block, input) } /// Calculate used value of inline-size just like we do for inline replaced elements. fn initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, _: &LayoutContext) -> MaybeAuto { let fragment = block.fragment(); fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size); // For replaced block flow, the rest of the constraint solving will // take inline-size to be specified as the value computed here. MaybeAuto::Specified(fragment.content_inline_size()) } } impl ISizeAndMarginsComputer for FloatNonReplaced { /// CSS Section 10.3.5 /// /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size, input.inline_start_margin, input.inline_end_margin, input.available_inline_size); let margin_inline_start = inline_start_margin.specified_or_zero(); let margin_inline_end = inline_end_margin.specified_or_zero(); let available_inline_size_float = available_inline_size - margin_inline_start - margin_inline_end; let shrink_to_fit = block.get_shrink_to_fit_inline_size(available_inline_size_float); let inline_size = computed_inline_size.specified_or_default(shrink_to_fit); debug!("assign_inline_sizes_float -- inline_size: {:?}", inline_size); ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end) } } impl ISizeAndMarginsComputer for FloatReplaced { /// CSS Section 10.3.5 /// /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size. fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let (computed_inline_size, inline_start_margin, inline_end_margin) = (input.computed_inline_size, input.inline_start_margin, input.inline_end_margin); let margin_inline_start = inline_start_margin.specified_or_zero(); let margin_inline_end = inline_end_margin.specified_or_zero(); let inline_size = match computed_inline_size { MaybeAuto::Specified(w) => w, MaybeAuto::Auto => panic!("FloatReplaced: inline_size should have been computed by now") }; debug!("assign_inline_sizes_float -- inline_size: {:?}", inline_size); ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end) } /// Calculate used value of inline-size just like we do for inline replaced elements. fn initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, _: &LayoutContext) -> MaybeAuto { let fragment = block.fragment(); fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size); // For replaced block flow, the rest of the constraint solving will // take inline-size to be specified as the value computed here. MaybeAuto::Specified(fragment.content_inline_size()) } } impl ISizeAndMarginsComputer for InlineBlockNonReplaced { /// Compute inline-start and inline-end margins and inline-size. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size, input.inline_start_margin, input.inline_end_margin, input.available_inline_size); // For inline-blocks, `auto` margins compute to 0. let inline_start_margin = inline_start_margin.specified_or_zero(); let inline_end_margin = inline_end_margin.specified_or_zero(); // If inline-size is set to 'auto', and this is an inline block, use the // shrink to fit algorithm (see CSS 2.1 § 10.3.9) let inline_size = match computed_inline_size { MaybeAuto::Auto => { block.get_shrink_to_fit_inline_size(available_inline_size - (inline_start_margin + inline_end_margin)) } MaybeAuto::Specified(inline_size) => inline_size, }; ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin) } } impl ISizeAndMarginsComputer for InlineBlockReplaced { /// Compute inline-start and inline-end margins and inline-size. /// /// ISize has already been calculated. We now calculate the margins just /// like for non-replaced blocks. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { debug_assert!(match input.computed_inline_size { MaybeAuto::Specified(_) => true, MaybeAuto::Auto => false, }); let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size, input.inline_start_margin, input.inline_end_margin, input.available_inline_size); // For inline-blocks, `auto` margins compute to 0. let inline_start_margin = inline_start_margin.specified_or_zero(); let inline_end_margin = inline_end_margin.specified_or_zero(); // If inline-size is set to 'auto', and this is an inline block, use the // shrink to fit algorithm (see CSS 2.1 § 10.3.9) let inline_size = match computed_inline_size { MaybeAuto::Auto => { block.get_shrink_to_fit_inline_size(available_inline_size - (inline_start_margin + inline_end_margin)) } MaybeAuto::Specified(inline_size) => inline_size, }; ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin) } /// Calculate used value of inline-size just like we do for inline replaced elements. fn initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, _: &LayoutContext) -> MaybeAuto { let fragment = block.fragment(); fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size); // For replaced block flow, the rest of the constraint solving will // take inline-size to be specified as the value computed here. MaybeAuto::Specified(fragment.content_inline_size()) } }