layout: Check flow descendants of inline block fragments to find their

baselines when aligning inline fragments per CSS 2.1 § 10.8.1.
This commit is contained in:
Patrick Walton 2016-04-27 12:44:43 -07:00
parent 8823f87276
commit 04f05349b1
6 changed files with 191 additions and 65 deletions

View file

@ -33,7 +33,8 @@ use display_list_builder::BlockFlowDisplayListBuilding;
use display_list_builder::{BorderPaintingMode, DisplayListBuildState, FragmentDisplayListBuilding};
use euclid::{Point2D, Rect, Size2D};
use floats::{ClearType, FloatKind, Floats, PlacementInfo};
use flow::{BLOCK_POSITION_IS_STATIC, CLEARS_LEFT, CLEARS_RIGHT, INLINE_POSITION_IS_STATIC};
use flow::{BLOCK_POSITION_IS_STATIC, CLEARS_LEFT, CLEARS_RIGHT};
use flow::{CONTAINS_TEXT_OR_REPLACED_FRAGMENTS, INLINE_POSITION_IS_STATIC};
use flow::{IS_ABSOLUTELY_POSITIONED};
use flow::{ImmutableFlowUtils, LateAbsolutePositionInfo, MutableFlowUtils, OpaqueFlow};
use flow::{NEEDS_LAYER, PostorderFlowTraversal, PreorderFlowTraversal, FragmentationContext};
@ -1477,18 +1478,41 @@ impl BlockFlow {
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 flags = self.base.flags;
let mut flags = self.base.flags;
if self.definitely_has_zero_block_size() {
// This is kind of a hack for Acid2. But it's a harmless one, because (a) this behavior
// is unspecified; (b) it matches the behavior one would intuitively expect, since
// floats don't flow around blocks that take up no space in the block direction.
flags.remove(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
} else if self.fragment.is_text_or_replaced() {
flags.insert(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
} else {
flags.remove(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
for kid in self.base.children.iter() {
if flow::base(kid).flags.contains(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS) {
flags.insert(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
break
}
}
}
// Find the maximum inline-size from children.
//
// See: https://lists.w3.org/Archives/Public/www-style/2014Nov/0085.html
//
// FIXME(pcwalton): This doesn't exactly follow that algorithm at the moment.
// FIXME(pcwalton): This should consider all float descendants, not just 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));
let mut preferred_inline_size_of_children_without_text_or_replaced_fragments = Au(0);
for kid in self.base.child_iter_mut() {
let is_absolutely_positioned =
flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED);
if flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED) || !consult_children {
continue
}
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);
@ -1502,32 +1526,37 @@ impl BlockFlow {
right_float_width_accumulator = Au(0)
}
match float_kind {
float::T::none => {
match (float_kind, child_base.flags.contains(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS)) {
(float::T::none, true) => {
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 => {
(float::T::none, false) => {
preferred_inline_size_of_children_without_text_or_replaced_fragments = max(
preferred_inline_size_of_children_without_text_or_replaced_fragments,
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 => {
(float::T::right, _) => {
right_float_width_accumulator = right_float_width_accumulator +
child_base.intrinsic_inline_sizes.preferred_inline_size;
}
}
}
}
// 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 =
computation.content_intrinsic_sizes.preferred_inline_size + left_float_width +
right_float_width;
computation.content_intrinsic_sizes.preferred_inline_size =
max(computation.content_intrinsic_sizes.preferred_inline_size,
left_float_width + right_float_width);
preferred_inline_size_of_children_without_text_or_replaced_fragments);
self.base.intrinsic_inline_sizes = computation.finish();
self.base.flags = flags
@ -1630,6 +1659,18 @@ impl BlockFlow {
FormattingContextType::None | FormattingContextType::Other => {}
}
}
fn definitely_has_zero_block_size(&self) -> bool {
if !self.fragment.style.content_block_size().is_definitely_zero() {
return false
}
let border_width = self.fragment.border_width();
if border_width.block_start != Au(0) || border_width.block_end != Au(0) {
return false
}
let padding = self.fragment.style.logical_padding();
padding.block_start.is_definitely_zero() && padding.block_end.is_definitely_zero()
}
}
impl Flow for BlockFlow {

View file

@ -512,6 +512,8 @@ pub trait ImmutableFlowUtils {
/// Returns true if floats might flow through this flow, as determined by the float placement
/// speculation pass.
fn floats_might_flow_through(self) -> bool;
fn baseline_offset_of_last_line_box_in_flow(self) -> Option<Au>;
}
pub trait MutableFlowUtils {
@ -634,45 +636,48 @@ bitflags! {
#[doc = "Whether this flow must have its own layer. Even if this flag is not set, it might"]
#[doc = "get its own layer if it's deemed to be likely to overlap flows with their own"]
#[doc = "layer."]
const NEEDS_LAYER = 0b0000_0000_0000_0010_0000,
const NEEDS_LAYER = 0b0000_0000_0000_0000_0010_0000,
#[doc = "Whether this flow is absolutely positioned. This is checked all over layout, so a"]
#[doc = "virtual call is too expensive."]
const IS_ABSOLUTELY_POSITIONED = 0b0000_0000_0000_0100_0000,
const IS_ABSOLUTELY_POSITIONED = 0b0000_0000_0000_0000_0100_0000,
#[doc = "Whether this flow clears to the left. This is checked all over layout, so a"]
#[doc = "virtual call is too expensive."]
const CLEARS_LEFT = 0b0000_0000_0000_1000_0000,
const CLEARS_LEFT = 0b0000_0000_0000_0000_1000_0000,
#[doc = "Whether this flow clears to the right. This is checked all over layout, so a"]
#[doc = "virtual call is too expensive."]
const CLEARS_RIGHT = 0b0000_0000_0001_0000_0000,
const CLEARS_RIGHT = 0b0000_0000_0000_0001_0000_0000,
#[doc = "Whether this flow is left-floated. This is checked all over layout, so a"]
#[doc = "virtual call is too expensive."]
const FLOATS_LEFT = 0b0000_0000_0010_0000_0000,
const FLOATS_LEFT = 0b0000_0000_0000_0010_0000_0000,
#[doc = "Whether this flow is right-floated. This is checked all over layout, so a"]
#[doc = "virtual call is too expensive."]
const FLOATS_RIGHT = 0b0000_0000_0100_0000_0000,
const FLOATS_RIGHT = 0b0000_0000_0000_0100_0000_0000,
#[doc = "Text alignment. \
NB: If you update this, update `TEXT_ALIGN_SHIFT` below."]
const TEXT_ALIGN = 0b0000_0111_1000_0000_0000,
const TEXT_ALIGN = 0b0000_0000_0111_1000_0000_0000,
#[doc = "Whether this flow has a fragment with `counter-reset` or `counter-increment` \
styles."]
const AFFECTS_COUNTERS = 0b0000_1000_0000_0000_0000,
const AFFECTS_COUNTERS = 0b0000_0000_1000_0000_0000_0000,
#[doc = "Whether this flow's descendants have fragments that affect `counter-reset` or \
`counter-increment` styles."]
const HAS_COUNTER_AFFECTING_CHILDREN = 0b0001_0000_0000_0000_0000,
const HAS_COUNTER_AFFECTING_CHILDREN = 0b0000_0001_0000_0000_0000_0000,
#[doc = "Whether this flow behaves as though it had `position: static` for the purposes \
of positioning in the inline direction. This is set for flows with `position: \
static` and `position: relative` as well as absolutely-positioned flows with \
unconstrained positions in the inline direction."]
const INLINE_POSITION_IS_STATIC = 0b0010_0000_0000_0000_0000,
const INLINE_POSITION_IS_STATIC = 0b0000_0010_0000_0000_0000_0000,
#[doc = "Whether this flow behaves as though it had `position: static` for the purposes \
of positioning in the block direction. This is set for flows with `position: \
static` and `position: relative` as well as absolutely-positioned flows with \
unconstrained positions in the block direction."]
const BLOCK_POSITION_IS_STATIC = 0b0100_0000_0000_0000_0000,
const BLOCK_POSITION_IS_STATIC = 0b0000_0100_0000_0000_0000_0000,
/// Whether any ancestor is a fragmentation container
const CAN_BE_FRAGMENTED = 0b1000_0000_0000_0000_0000,
const CAN_BE_FRAGMENTED = 0b0000_1000_0000_0000_0000_0000,
/// Whether this flow contains any text and/or replaced fragments.
const CONTAINS_TEXT_OR_REPLACED_FRAGMENTS = 0b0001_0000_0000_0000_0000_0000,
}
}
@ -1387,6 +1392,21 @@ impl<'a> ImmutableFlowUtils for &'a Flow {
}
self.as_block().formatting_context_type() == FormattingContextType::None
}
fn baseline_offset_of_last_line_box_in_flow(self) -> Option<Au> {
for kid in base(self).children.iter().rev() {
if kid.is_inline_flow() {
return kid.as_inline().baseline_offset_of_last_line()
}
if kid.is_block_like() &&
kid.as_block().formatting_context_type() == FormattingContextType::None {
if let Some(baseline_offset) = kid.baseline_offset_of_last_line_box_in_flow() {
return Some(base(kid).position.start.b + baseline_offset)
}
}
}
None
}
}
impl<'a> MutableFlowUtils for &'a mut Flow {

View file

@ -111,6 +111,12 @@ impl<'a> Iterator for FlowListIterator<'a> {
}
}
impl<'a> DoubleEndedIterator for FlowListIterator<'a> {
fn next_back(&mut self) -> Option<&'a Flow> {
self.it.next_back().map(|x| &**x)
}
}
impl<'a> Iterator for MutFlowListIterator<'a> {
type Item = &'a mut Flow;
#[inline]

View file

@ -1077,7 +1077,7 @@ impl Fragment {
/// Returns the sum of the inline-sizes of all the borders of this fragment. Note that this
/// can be expensive to compute, so if possible use the `border_padding` field instead.
#[inline]
fn border_width(&self) -> LogicalMargin<Au> {
pub fn border_width(&self) -> LogicalMargin<Au> {
let style_border_width = match self.specific {
SpecificFragmentInfo::ScannedText(_) |
SpecificFragmentInfo::InlineBlock(_) => LogicalMargin::zero(self.style.writing_mode),
@ -1983,14 +1983,19 @@ impl Fragment {
}
SpecificFragmentInfo::InlineBlock(ref info) => {
// See CSS 2.1 § 10.8.1.
let block_flow = info.flow_ref.as_block();
let font_style = self.style.get_font_arc();
let font_metrics = text::font_metrics_for_style(&mut layout_context.font_context(),
font_style);
InlineMetrics::from_block_height(&font_metrics,
block_flow.base.position.size.block,
block_flow.fragment.margin.block_start,
block_flow.fragment.margin.block_end)
let flow = &info.flow_ref;
let block_flow = flow.as_block();
let baseline_offset = match flow.baseline_offset_of_last_line_box_in_flow() {
Some(baseline_offset) => baseline_offset,
None => block_flow.fragment.border_box.size.block,
};
let start_margin = block_flow.fragment.margin.block_start;
let end_margin = block_flow.fragment.margin.block_end;
let depth_below_baseline = flow::base(&**flow).position.size.block -
baseline_offset + end_margin;
InlineMetrics::new(baseline_offset + start_margin,
depth_below_baseline,
baseline_offset)
}
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineAbsolute(_) => {
@ -2552,6 +2557,28 @@ impl Fragment {
pub fn layer_id_for_overflow_scroll(&self) -> LayerId {
LayerId::new_of_type(LayerType::OverflowScroll, self.node.id() as usize)
}
pub fn is_text_or_replaced(&self) -> bool {
match self.specific {
SpecificFragmentInfo::Generic |
SpecificFragmentInfo::InlineAbsolute(_) |
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineBlock(_) |
SpecificFragmentInfo::Multicol |
SpecificFragmentInfo::MulticolColumn |
SpecificFragmentInfo::Table |
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableColumn(_) |
SpecificFragmentInfo::TableRow |
SpecificFragmentInfo::TableWrapper => false,
SpecificFragmentInfo::Canvas(_) |
SpecificFragmentInfo::GeneratedContent(_) |
SpecificFragmentInfo::Iframe(_) |
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::ScannedText(_) |
SpecificFragmentInfo::UnscannedText(_) => true
}
}
}
impl fmt::Debug for Fragment {

View file

@ -11,7 +11,8 @@ use display_list_builder::DisplayListBuildState;
use display_list_builder::{FragmentDisplayListBuilding, InlineFlowDisplayListBuilding};
use euclid::{Point2D, Size2D};
use floats::{FloatKind, Floats, PlacementInfo};
use flow::{EarlyAbsolutePositionInfo, MutableFlowUtils, OpaqueFlow};
use flow::{CONTAINS_TEXT_OR_REPLACED_FRAGMENTS, EarlyAbsolutePositionInfo, MutableFlowUtils};
use flow::{OpaqueFlow};
use flow::{self, BaseFlow, Flow, FlowClass, ForceNonfloatedFlag, IS_ABSOLUTELY_POSITIONED};
use flow_ref;
use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, Overflow};
@ -1275,6 +1276,16 @@ impl InlineFlow {
}
}
}
pub fn baseline_offset_of_last_line(&self) -> Option<Au> {
match self.lines.last() {
None => None,
Some(ref last_line) => {
Some(last_line.bounds.start.b + last_line.bounds.size.block -
last_line.inline_metrics.depth_below_baseline)
}
}
}
}
impl Flow for InlineFlow {
@ -1300,6 +1311,8 @@ impl Flow for InlineFlow {
flow::mut_base(kid).floats = Floats::new(writing_mode);
}
self.base.flags.remove(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
let mut intrinsic_sizes_for_flow = IntrinsicISizesContribution::new();
let mut intrinsic_sizes_for_inline_run = IntrinsicISizesContribution::new();
let mut intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new();
@ -1358,6 +1371,10 @@ impl Flow for InlineFlow {
}
fragment.restyle_damage.remove(BUBBLE_ISIZES);
if fragment.is_text_or_replaced() {
self.base.flags.insert(CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
}
}
// Flush any remaining nonbroken-run and inline-run intrinsic sizes.

View file

@ -1837,6 +1837,21 @@ pub mod computed {
Auto,
Calc(CalcLengthOrPercentage),
}
impl LengthOrPercentageOrAuto {
/// Returns true if the computed value is absolute 0 or 0%.
///
/// (Returns false for calc() values, even if ones that may resolve to zero.)
#[inline]
pub fn is_definitely_zero(&self) -> bool {
use self::LengthOrPercentageOrAuto::*;
match *self {
Length(Au(0)) | Percentage(0.0) => true,
Length(_) | Percentage(_) | Calc(_) | Auto => false
}
}
}
impl fmt::Debug for LengthOrPercentageOrAuto {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {