Auto merge of #25033 - servo:intrinsic, r=nox

Add support for inline-block and for computing min/max-content
This commit is contained in:
bors-servo 2019-12-04 16:23:33 -05:00 committed by GitHub
commit e70397d90a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 823 additions and 272 deletions

View file

@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::context::LayoutContext;
use crate::element_data::{LayoutBox, LayoutDataForElement}; use crate::element_data::{LayoutBox, LayoutDataForElement};
use crate::geom::physical::Vec2; use crate::geom::physical::Vec2;
use crate::replaced::ReplacedContent; use crate::replaced::ReplacedContent;
@ -13,7 +14,6 @@ use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use servo_arc::Arc as ServoArc; use servo_arc::Arc as ServoArc;
use std::marker::PhantomData as marker; use std::marker::PhantomData as marker;
use std::sync::Arc; use std::sync::Arc;
use style::context::SharedStyleContext;
use style::dom::TNode; use style::dom::TNode;
use style::properties::ComputedValues; use style::properties::ComputedValues;
use style::selector_parser::PseudoElement; use style::selector_parser::PseudoElement;
@ -66,7 +66,7 @@ where
fn traverse_children_of<'dom, Node>( fn traverse_children_of<'dom, Node>(
parent_element: Node, parent_element: Node,
context: &SharedStyleContext, context: &LayoutContext,
handler: &mut impl TraversalHandler<'dom, Node>, handler: &mut impl TraversalHandler<'dom, Node>,
) where ) where
Node: NodeExt<'dom>, Node: NodeExt<'dom>,
@ -88,7 +88,7 @@ fn traverse_children_of<'dom, Node>(
fn traverse_element<'dom, Node>( fn traverse_element<'dom, Node>(
element: Node, element: Node,
context: &SharedStyleContext, context: &LayoutContext,
handler: &mut impl TraversalHandler<'dom, Node>, handler: &mut impl TraversalHandler<'dom, Node>,
) where ) where
Node: NodeExt<'dom>, Node: NodeExt<'dom>,
@ -121,7 +121,7 @@ fn traverse_element<'dom, Node>(
fn traverse_pseudo_element<'dom, Node>( fn traverse_pseudo_element<'dom, Node>(
which: WhichPseudoElement, which: WhichPseudoElement,
element: Node, element: Node,
context: &SharedStyleContext, context: &LayoutContext,
handler: &mut impl TraversalHandler<'dom, Node>, handler: &mut impl TraversalHandler<'dom, Node>,
) where ) where
Node: NodeExt<'dom>, Node: NodeExt<'dom>,
@ -146,7 +146,7 @@ fn traverse_pseudo_element<'dom, Node>(
fn traverse_pseudo_element_contents<'dom, Node>( fn traverse_pseudo_element_contents<'dom, Node>(
pseudo_element_style: &ServoArc<ComputedValues>, pseudo_element_style: &ServoArc<ComputedValues>,
context: &SharedStyleContext, context: &LayoutContext,
handler: &mut impl TraversalHandler<'dom, Node>, handler: &mut impl TraversalHandler<'dom, Node>,
items: Vec<PseudoElementContentItem>, items: Vec<PseudoElementContentItem>,
) where ) where
@ -159,9 +159,10 @@ fn traverse_pseudo_element_contents<'dom, Node>(
PseudoElementContentItem::Replaced(contents) => { PseudoElementContentItem::Replaced(contents) => {
let item_style = anonymous_style.get_or_insert_with(|| { let item_style = anonymous_style.get_or_insert_with(|| {
context context
.shared_context()
.stylist .stylist
.style_for_anonymous::<Node::ConcreteElement>( .style_for_anonymous::<Node::ConcreteElement>(
&context.guards, &context.shared_context().guards,
&PseudoElement::ServoText, &PseudoElement::ServoText,
&pseudo_element_style, &pseudo_element_style,
) )
@ -187,6 +188,16 @@ fn traverse_pseudo_element_contents<'dom, Node>(
} }
} }
impl<Node> Contents<Node> {
/// Returns true iff the `try_from` impl below would return `Err(_)`
pub fn is_replaced(&self) -> bool {
match self {
Contents::OfElement(_) | Contents::OfPseudoElement(_) => false,
Contents::Replaced(_) => true,
}
}
}
impl<Node> std::convert::TryFrom<Contents<Node>> for NonReplacedContents<Node> { impl<Node> std::convert::TryFrom<Contents<Node>> for NonReplacedContents<Node> {
type Error = ReplacedContent; type Error = ReplacedContent;
@ -215,7 +226,7 @@ where
pub(crate) fn traverse( pub(crate) fn traverse(
self, self,
inherited_style: &ServoArc<ComputedValues>, inherited_style: &ServoArc<ComputedValues>,
context: &SharedStyleContext, context: &LayoutContext,
handler: &mut impl TraversalHandler<'dom, Node>, handler: &mut impl TraversalHandler<'dom, Node>,
) { ) {
match self { match self {
@ -230,7 +241,7 @@ where
fn pseudo_element_style<'dom, Node>( fn pseudo_element_style<'dom, Node>(
_which: WhichPseudoElement, _which: WhichPseudoElement,
_element: Node, _element: Node,
_context: &SharedStyleContext, _context: &LayoutContext,
) -> Option<ServoArc<ComputedValues>> ) -> Option<ServoArc<ComputedValues>>
where where
Node: NodeExt<'dom>, Node: NodeExt<'dom>,
@ -243,7 +254,7 @@ where
fn generate_pseudo_element_content<'dom, Node>( fn generate_pseudo_element_content<'dom, Node>(
_pseudo_element_style: &ComputedValues, _pseudo_element_style: &ComputedValues,
_element: Node, _element: Node,
_context: &SharedStyleContext, _context: &LayoutContext,
) -> Vec<PseudoElementContentItem> ) -> Vec<PseudoElementContentItem>
where where
Node: NodeExt<'dom>, Node: NodeExt<'dom>,
@ -292,7 +303,7 @@ pub(crate) trait NodeExt<'dom>: 'dom + Copy + LayoutNode + Send + Sync {
fn first_child(self) -> Option<Self>; fn first_child(self) -> Option<Self>;
fn next_sibling(self) -> Option<Self>; fn next_sibling(self) -> Option<Self>;
fn parent_node(self) -> Option<Self>; fn parent_node(self) -> Option<Self>;
fn style(self, context: &SharedStyleContext) -> ServoArc<ComputedValues>; fn style(self, context: &LayoutContext) -> ServoArc<ComputedValues>;
fn layout_data_mut(&self) -> AtomicRefMut<LayoutDataForElement>; fn layout_data_mut(&self) -> AtomicRefMut<LayoutDataForElement>;
fn element_box_slot(&self) -> BoxSlot<'dom>; fn element_box_slot(&self) -> BoxSlot<'dom>;
@ -349,8 +360,8 @@ where
TNode::parent_node(&self) TNode::parent_node(&self)
} }
fn style(self, context: &SharedStyleContext) -> ServoArc<ComputedValues> { fn style(self, context: &LayoutContext) -> ServoArc<ComputedValues> {
self.to_threadsafe().style(context) self.to_threadsafe().style(context.shared_context())
} }
fn layout_data_mut(&self) -> AtomicRefMut<LayoutDataForElement> { fn layout_data_mut(&self) -> AtomicRefMut<LayoutDataForElement> {

View file

@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::context::LayoutContext;
use crate::dom_traversal::{BoxSlot, Contents, NodeExt, NonReplacedContents, TraversalHandler}; use crate::dom_traversal::{BoxSlot, Contents, NodeExt, NonReplacedContents, TraversalHandler};
use crate::element_data::LayoutBox; use crate::element_data::LayoutBox;
use crate::flow::float::FloatBox; use crate::flow::float::FloatBox;
@ -9,26 +10,31 @@ use crate::flow::inline::{InlineBox, InlineFormattingContext, InlineLevelBox, Te
use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox}; use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
use crate::formatting_contexts::IndependentFormattingContext; use crate::formatting_contexts::IndependentFormattingContext;
use crate::positioned::AbsolutelyPositionedBox; use crate::positioned::AbsolutelyPositionedBox;
use crate::style_ext::{DisplayGeneratingBox, DisplayInside, DisplayOutside}; use crate::sizing::{BoxContentSizes, ContentSizes, ContentSizesRequest};
use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox, DisplayInside, DisplayOutside};
use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::iter::{IntoParallelIterator, ParallelIterator};
use rayon_croissant::ParallelIteratorExt; use rayon_croissant::ParallelIteratorExt;
use servo_arc::Arc; use servo_arc::Arc;
use std::convert::TryInto; use std::convert::{TryFrom, TryInto};
use style::context::SharedStyleContext;
use style::properties::ComputedValues; use style::properties::ComputedValues;
use style::selector_parser::PseudoElement; use style::selector_parser::PseudoElement;
impl BlockFormattingContext { impl BlockFormattingContext {
pub fn construct<'dom>( pub fn construct<'dom>(
context: &SharedStyleContext<'_>, context: &LayoutContext,
style: &Arc<ComputedValues>, style: &Arc<ComputedValues>,
contents: NonReplacedContents<impl NodeExt<'dom>>, contents: NonReplacedContents<impl NodeExt<'dom>>,
) -> Self { content_sizes: ContentSizesRequest,
let (contents, contains_floats) = BlockContainer::construct(context, style, contents); ) -> (Self, BoxContentSizes) {
Self { let (contents, contains_floats, inline_content_sizes) =
BlockContainer::construct(context, style, contents, content_sizes);
// FIXME: add contribution to `inline_content_sizes` of floats in this formatting context
// https://dbaron.org/css/intrinsic/#intrinsic
let bfc = Self {
contents, contents,
contains_floats: contains_floats == ContainsFloats::Yes, contains_floats: contains_floats == ContainsFloats::Yes,
} };
(bfc, inline_content_sizes)
} }
} }
@ -71,7 +77,7 @@ enum IntermediateBlockContainer<Node> {
/// This builder starts from the first child of a given DOM node /// This builder starts from the first child of a given DOM node
/// and does a preorder traversal of all of its inclusive siblings. /// and does a preorder traversal of all of its inclusive siblings.
struct BlockContainerBuilder<'dom, 'style, Node> { struct BlockContainerBuilder<'dom, 'style, Node> {
context: &'style SharedStyleContext<'style>, context: &'style LayoutContext<'style>,
block_container_style: &'style Arc<ComputedValues>, block_container_style: &'style Arc<ComputedValues>,
@ -123,19 +129,20 @@ struct BlockContainerBuilder<'dom, 'style, Node> {
} }
impl BlockContainer { impl BlockContainer {
pub fn construct<'dom, 'style>( pub fn construct<'dom>(
context: &SharedStyleContext<'style>, context: &LayoutContext,
block_container_style: &Arc<ComputedValues>, block_container_style: &Arc<ComputedValues>,
contents: NonReplacedContents<impl NodeExt<'dom>>, contents: NonReplacedContents<impl NodeExt<'dom>>,
) -> (BlockContainer, ContainsFloats) { content_sizes: ContentSizesRequest,
) -> (BlockContainer, ContainsFloats, BoxContentSizes) {
let mut builder = BlockContainerBuilder { let mut builder = BlockContainerBuilder {
context, context,
block_container_style, block_container_style,
block_level_boxes: Default::default(), block_level_boxes: Vec::new(),
ongoing_inline_formatting_context: Default::default(), ongoing_inline_formatting_context: InlineFormattingContext::default(),
ongoing_inline_boxes_stack: Default::default(), ongoing_inline_boxes_stack: Vec::new(),
anonymous_style: Default::default(), anonymous_style: None,
contains_floats: Default::default(), contains_floats: ContainsFloats::No,
}; };
contents.traverse(block_container_style, context, &mut builder); contents.traverse(block_container_style, context, &mut builder);
@ -148,32 +155,65 @@ impl BlockContainer {
.is_empty() .is_empty()
{ {
if builder.block_level_boxes.is_empty() { if builder.block_level_boxes.is_empty() {
let content_sizes = content_sizes.compute(|| {
builder
.ongoing_inline_formatting_context
.inline_content_sizes(context)
});
let container = BlockContainer::InlineFormattingContext( let container = BlockContainer::InlineFormattingContext(
builder.ongoing_inline_formatting_context, builder.ongoing_inline_formatting_context,
); );
return (container, builder.contains_floats); return (container, builder.contains_floats, content_sizes);
} }
builder.end_ongoing_inline_formatting_context(); builder.end_ongoing_inline_formatting_context();
} }
let mut contains_floats = builder.contains_floats; type Intermediate<Node> = IntermediateBlockLevelBox<Node>;
let container = BlockContainer::BlockLevelBoxes( struct Target {
builder contains_floats: ContainsFloats,
.block_level_boxes outer_content_sizes_of_children: ContentSizes,
.into_par_iter() }
.mapfold_reduce_into( impl Default for Target {
&mut contains_floats, fn default() -> Self {
|contains_floats, (intermediate, box_slot): (IntermediateBlockLevelBox<_>, BoxSlot<'_>)| { Self {
let (block_level_box, box_contains_floats) = intermediate.finish(context); contains_floats: ContainsFloats::No,
*contains_floats |= box_contains_floats; outer_content_sizes_of_children: ContentSizes::zero(),
box_slot.set(LayoutBox::BlockLevel(block_level_box.clone())); }
block_level_box }
}, }
|left, right| *left |= right, let mut target = Target {
) contains_floats: builder.contains_floats,
.collect(), outer_content_sizes_of_children: ContentSizes::zero(),
};
let iter = builder.block_level_boxes.into_par_iter();
let iter = iter.mapfold_reduce_into(
&mut target,
|target, (intermediate, box_slot): (Intermediate<_>, BoxSlot<'_>)| {
let (block_level_box, box_contains_floats) = intermediate.finish(
context,
content_sizes
.if_requests_inline(|| &mut target.outer_content_sizes_of_children),
);
target.contains_floats |= box_contains_floats;
box_slot.set(LayoutBox::BlockLevel(block_level_box.clone()));
block_level_box
},
|left, right| {
left.contains_floats |= right.contains_floats;
if content_sizes.requests_inline() {
left.outer_content_sizes_of_children
.max_assign(&right.outer_content_sizes_of_children)
}
},
); );
(container, contains_floats) let container = BlockContainer::BlockLevelBoxes(iter.collect());
let Target {
contains_floats,
outer_content_sizes_of_children,
} = target;
let content_sizes = content_sizes.compute(|| outer_content_sizes_of_children);
(container, contains_floats, content_sizes)
} }
} }
@ -324,38 +364,38 @@ where
display_inside: DisplayInside, display_inside: DisplayInside,
contents: Contents<Node>, contents: Contents<Node>,
) -> Arc<InlineLevelBox> { ) -> Arc<InlineLevelBox> {
let box_ = match contents.try_into() { let box_ = if display_inside == DisplayInside::Flow && !contents.is_replaced() {
Err(replaced) => Arc::new(InlineLevelBox::Atomic( // We found un inline box.
// Whatever happened before, all we need to do before recurring
// is to remember this ongoing inline level box.
self.ongoing_inline_boxes_stack.push(InlineBox {
style: style.clone(),
first_fragment: true,
last_fragment: false,
children: vec![],
});
// `unwrap` doesnt panic here because `is_replaced` returned `false`.
NonReplacedContents::try_from(contents)
.unwrap()
.traverse(&style, self.context, self);
let mut inline_box = self
.ongoing_inline_boxes_stack
.pop()
.expect("no ongoing inline level box found");
inline_box.last_fragment = true;
Arc::new(InlineLevelBox::InlineBox(inline_box))
} else {
Arc::new(InlineLevelBox::Atomic(
IndependentFormattingContext::construct( IndependentFormattingContext::construct(
self.context, self.context,
style.clone(), style.clone(),
display_inside, display_inside,
<Contents<Node>>::Replaced(replaced), contents,
ContentSizesRequest::inline_if(style.inline_size_is_auto()),
), ),
)), ))
Ok(non_replaced) => match display_inside {
DisplayInside::Flow |
// TODO: Properly implement display: inline-block.
DisplayInside::FlowRoot => {
// Whatever happened before, we just found an inline level element, so
// all we need to do is to remember this ongoing inline level box.
self.ongoing_inline_boxes_stack.push(InlineBox {
style: style.clone(),
first_fragment: true,
last_fragment: false,
children: vec![],
});
NonReplacedContents::traverse(non_replaced, &style, self.context, self);
let mut inline_box = self
.ongoing_inline_boxes_stack
.pop()
.expect("no ongoing inline level box found");
inline_box.last_fragment = true;
Arc::new(InlineLevelBox::InlineBox(inline_box))
},
},
}; };
self.current_inline_level_boxes().push(box_.clone()); self.current_inline_level_boxes().push(box_.clone());
box_ box_
@ -451,14 +491,7 @@ where
self.block_level_boxes.push((box_, box_slot)); self.block_level_boxes.push((box_, box_slot));
} else { } else {
let box_ = Arc::new(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox( let box_ = Arc::new(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(
AbsolutelyPositionedBox { AbsolutelyPositionedBox::construct(self.context, style, display_inside, contents),
contents: IndependentFormattingContext::construct(
self.context,
style,
display_inside,
contents,
),
},
)); ));
self.current_inline_level_boxes().push(box_.clone()); self.current_inline_level_boxes().push(box_.clone());
box_slot.set(LayoutBox::InlineLevel(box_)) box_slot.set(LayoutBox::InlineLevel(box_))
@ -482,14 +515,12 @@ where
}; };
self.block_level_boxes.push((box_, box_slot)); self.block_level_boxes.push((box_, box_slot));
} else { } else {
let box_ = Arc::new(InlineLevelBox::OutOfFlowFloatBox(FloatBox { let box_ = Arc::new(InlineLevelBox::OutOfFlowFloatBox(FloatBox::construct(
contents: IndependentFormattingContext::construct( self.context,
self.context, style,
style, display_inside,
display_inside, contents,
contents, )));
),
}));
self.current_inline_level_boxes().push(box_.clone()); self.current_inline_level_boxes().push(box_.clone());
box_slot.set(LayoutBox::InlineLevel(box_)) box_slot.set(LayoutBox::InlineLevel(box_))
} }
@ -509,9 +540,10 @@ where
let block_container_style = self.block_container_style; let block_container_style = self.block_container_style;
let anonymous_style = self.anonymous_style.get_or_insert_with(|| { let anonymous_style = self.anonymous_style.get_or_insert_with(|| {
context context
.shared_context()
.stylist .stylist
.style_for_anonymous::<Node::ConcreteElement>( .style_for_anonymous::<Node::ConcreteElement>(
&context.guards, &context.shared_context().guards,
&PseudoElement::ServoText, &PseudoElement::ServoText,
&block_container_style, &block_container_style,
) )
@ -546,13 +578,24 @@ impl<'dom, Node> IntermediateBlockLevelBox<Node>
where where
Node: NodeExt<'dom>, Node: NodeExt<'dom>,
{ {
fn finish<'style>( fn finish(
self, self,
context: &SharedStyleContext<'style>, context: &LayoutContext,
max_assign_in_flow_outer_content_sizes_to: Option<&mut ContentSizes>,
) -> (Arc<BlockLevelBox>, ContainsFloats) { ) -> (Arc<BlockLevelBox>, ContainsFloats) {
match self { match self {
IntermediateBlockLevelBox::SameFormattingContextBlock { style, contents } => { IntermediateBlockLevelBox::SameFormattingContextBlock { style, contents } => {
let (contents, contains_floats) = contents.finish(context, &style); let (contents, contains_floats, box_content_sizes) = contents.finish(
context,
&style,
ContentSizesRequest::inline_if(
max_assign_in_flow_outer_content_sizes_to.is_some() &&
style.inline_size_is_auto(),
),
);
if let Some(to) = max_assign_in_flow_outer_content_sizes_to {
to.max_assign(&box_content_sizes.outer_inline(&style))
}
let block_level_box = let block_level_box =
Arc::new(BlockLevelBox::SameFormattingContextBlock { contents, style }); Arc::new(BlockLevelBox::SameFormattingContextBlock { contents, style });
(block_level_box, contains_floats) (block_level_box, contains_floats)
@ -562,12 +605,20 @@ where
display_inside, display_inside,
contents, contents,
} => { } => {
let content_sizes = ContentSizesRequest::inline_if(
max_assign_in_flow_outer_content_sizes_to.is_some() &&
style.inline_size_is_auto(),
);
let contents = IndependentFormattingContext::construct( let contents = IndependentFormattingContext::construct(
context, context,
style, style,
display_inside, display_inside,
contents, contents,
content_sizes,
); );
if let Some(to) = max_assign_in_flow_outer_content_sizes_to {
to.max_assign(&contents.content_sizes.outer_inline(&contents.style))
}
( (
Arc::new(BlockLevelBox::Independent(contents)), Arc::new(BlockLevelBox::Independent(contents)),
ContainsFloats::No, ContainsFloats::No,
@ -579,14 +630,7 @@ where
contents, contents,
} => { } => {
let block_level_box = Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox( let block_level_box = Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
AbsolutelyPositionedBox { AbsolutelyPositionedBox::construct(context, style, display_inside, contents),
contents: IndependentFormattingContext::construct(
context,
style,
display_inside,
contents,
),
},
)); ));
(block_level_box, ContainsFloats::No) (block_level_box, ContainsFloats::No)
}, },
@ -595,14 +639,9 @@ where
display_inside, display_inside,
contents, contents,
} => { } => {
let contents = IndependentFormattingContext::construct( let block_level_box = Arc::new(BlockLevelBox::OutOfFlowFloatBox(
context, FloatBox::construct(context, style, display_inside, contents),
style, ));
display_inside,
contents,
);
let block_level_box =
Arc::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox { contents }));
(block_level_box, ContainsFloats::Yes) (block_level_box, ContainsFloats::Yes)
}, },
} }
@ -613,22 +652,25 @@ impl<'dom, Node> IntermediateBlockContainer<Node>
where where
Node: NodeExt<'dom>, Node: NodeExt<'dom>,
{ {
fn finish<'style>( fn finish(
self, self,
context: &SharedStyleContext<'style>, context: &LayoutContext,
style: &Arc<ComputedValues>, style: &Arc<ComputedValues>,
) -> (BlockContainer, ContainsFloats) { content_sizes: ContentSizesRequest,
) -> (BlockContainer, ContainsFloats, BoxContentSizes) {
match self { match self {
IntermediateBlockContainer::Deferred { contents } => { IntermediateBlockContainer::Deferred { contents } => {
BlockContainer::construct(context, style, contents) BlockContainer::construct(context, style, contents, content_sizes)
}, },
IntermediateBlockContainer::InlineFormattingContext(ifc) => { IntermediateBlockContainer::InlineFormattingContext(ifc) => {
let content_sizes = content_sizes.compute(|| ifc.inline_content_sizes(context));
// If that inline formatting context contained any float, those // If that inline formatting context contained any float, those
// were already taken into account during the first phase of // were already taken into account during the first phase of
// box construction. // box construction.
( (
BlockContainer::InlineFormattingContext(ifc), BlockContainer::InlineFormattingContext(ifc),
ContainsFloats::No, ContainsFloats::No,
content_sizes,
) )
}, },
} }
@ -648,9 +690,3 @@ impl std::ops::BitOrAssign for ContainsFloats {
} }
} }
} }
impl Default for ContainsFloats {
fn default() -> Self {
ContainsFloats::No
}
}

View file

@ -2,7 +2,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::context::LayoutContext;
use crate::dom_traversal::{Contents, NodeExt};
use crate::formatting_contexts::IndependentFormattingContext; use crate::formatting_contexts::IndependentFormattingContext;
use crate::sizing::ContentSizesRequest;
use crate::style_ext::{ComputedValuesExt, DisplayInside};
use servo_arc::Arc;
use style::properties::ComputedValues;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct FloatBox { pub(crate) struct FloatBox {
@ -19,3 +25,23 @@ impl FloatContext {
FloatContext {} FloatContext {}
} }
} }
impl FloatBox {
pub fn construct<'dom>(
context: &LayoutContext,
style: Arc<ComputedValues>,
display_inside: DisplayInside,
contents: Contents<impl NodeExt<'dom>>,
) -> Self {
let content_sizes = ContentSizesRequest::inline_if(style.inline_size_is_auto());
Self {
contents: IndependentFormattingContext::construct(
context,
style,
display_inside,
contents,
content_sizes,
),
}
}
}

View file

@ -10,12 +10,16 @@ use crate::fragments::CollapsedBlockMargins;
use crate::fragments::{AnonymousFragment, BoxFragment, Fragment, TextFragment}; use crate::fragments::{AnonymousFragment, BoxFragment, Fragment, TextFragment};
use crate::geom::flow_relative::{Rect, Sides, Vec2}; use crate::geom::flow_relative::{Rect, Sides, Vec2};
use crate::positioned::{AbsolutelyPositionedBox, AbsolutelyPositionedFragment}; use crate::positioned::{AbsolutelyPositionedBox, AbsolutelyPositionedFragment};
use crate::sizing::ContentSizes;
use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayOutside}; use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayOutside};
use crate::{relative_adjustement, ContainingBlock}; use crate::{relative_adjustement, ContainingBlock};
use app_units::Au;
use gfx::text::text_run::GlyphRun;
use servo_arc::Arc; use servo_arc::Arc;
use style::properties::ComputedValues; use style::properties::ComputedValues;
use style::values::computed::Length; use style::values::computed::{Length, LengthPercentage, Percentage};
use style::Zero; use style::Zero;
use webrender_api::FontInstanceKey;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct InlineFormattingContext { pub(crate) struct InlineFormattingContext {
@ -63,8 +67,9 @@ struct PartialInlineBoxFragment<'box_tree> {
parent_nesting_level: InlineNestingLevelState<'box_tree>, parent_nesting_level: InlineNestingLevelState<'box_tree>,
} }
struct InlineFormattingContextState<'box_tree, 'cb> { struct InlineFormattingContextState<'box_tree, 'a> {
containing_block: &'cb ContainingBlock, absolutely_positioned_fragments: &'a mut Vec<AbsolutelyPositionedFragment<'box_tree>>,
containing_block: &'a ContainingBlock,
line_boxes: LinesBoxes, line_boxes: LinesBoxes,
inline_position: Length, inline_position: Length,
partial_inline_boxes_stack: Vec<PartialInlineBoxFragment<'box_tree>>, partial_inline_boxes_stack: Vec<PartialInlineBoxFragment<'box_tree>>,
@ -77,6 +82,114 @@ struct LinesBoxes {
} }
impl InlineFormattingContext { impl InlineFormattingContext {
// This works on an already-constructed `InlineFormattingContext`,
// Which would have to change if/when
// `BlockContainer::construct` parallelize their construction.
pub(super) fn inline_content_sizes(&self, layout_context: &LayoutContext) -> ContentSizes {
struct Computation {
paragraph: ContentSizes,
current_line: ContentSizes,
current_line_percentages: Percentage,
}
impl Computation {
fn traverse(
&mut self,
layout_context: &LayoutContext,
inline_level_boxes: &[Arc<InlineLevelBox>],
) {
for inline_level_box in inline_level_boxes {
match &**inline_level_box {
InlineLevelBox::InlineBox(inline_box) => {
let padding = inline_box.style.padding();
let border = inline_box.style.border_width();
let margin = inline_box.style.margin();
macro_rules! add {
($condition: ident, $side: ident) => {
if inline_box.$condition {
self.add_lengthpercentage(padding.$side);
self.add_length(border.$side);
if let Some(lp) = margin.$side.non_auto() {
self.add_lengthpercentage(lp)
}
}
};
}
add!(first_fragment, inline_start);
self.traverse(layout_context, &inline_box.children);
add!(last_fragment, inline_end);
},
InlineLevelBox::TextRun(text_run) => {
let BreakAndShapeResult {
runs,
break_at_start,
..
} = text_run.break_and_shape(layout_context);
if break_at_start {
self.line_break_opportunity()
}
for run in &runs {
let advance = Length::from(run.glyph_store.total_advance());
if run.glyph_store.is_whitespace() {
self.line_break_opportunity()
} else {
self.current_line.min_content += advance
}
self.current_line.max_content += advance
}
},
InlineLevelBox::Atomic(atomic) => {
let (outer, pc) = atomic
.content_sizes
.outer_inline_and_percentages(&atomic.style);
self.current_line.min_content += outer.min_content;
self.current_line.max_content += outer.max_content;
self.current_line_percentages += pc;
},
InlineLevelBox::OutOfFlowFloatBox(_) |
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => {},
}
}
}
fn add_lengthpercentage(&mut self, lp: LengthPercentage) {
self.add_length(lp.length_component());
self.current_line_percentages += lp.percentage_component();
}
fn add_length(&mut self, l: Length) {
self.current_line.min_content += l;
self.current_line.max_content += l;
}
fn line_break_opportunity(&mut self) {
self.paragraph
.min_content
.max_assign(take(&mut self.current_line.min_content));
}
fn forced_line_break(&mut self) {
self.line_break_opportunity();
self.current_line
.adjust_for_pbm_percentages(take(&mut self.current_line_percentages));
self.paragraph
.max_content
.max_assign(take(&mut self.current_line.max_content));
}
}
fn take<T: Zero>(x: &mut T) -> T {
std::mem::replace(x, T::zero())
}
let mut computation = Computation {
paragraph: ContentSizes::zero(),
current_line: ContentSizes::zero(),
current_line_percentages: Percentage::zero(),
};
computation.traverse(layout_context, &self.inline_level_boxes);
computation.forced_line_break();
computation.paragraph
}
pub(super) fn layout<'a>( pub(super) fn layout<'a>(
&'a self, &'a self,
layout_context: &LayoutContext, layout_context: &LayoutContext,
@ -85,6 +198,7 @@ impl InlineFormattingContext {
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>, absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
) -> FlowLayout { ) -> FlowLayout {
let mut ifc = InlineFormattingContextState { let mut ifc = InlineFormattingContextState {
absolutely_positioned_fragments,
containing_block, containing_block,
partial_inline_boxes_stack: Vec::new(), partial_inline_boxes_stack: Vec::new(),
line_boxes: LinesBoxes { line_boxes: LinesBoxes {
@ -107,10 +221,7 @@ impl InlineFormattingContext {
ifc.partial_inline_boxes_stack.push(partial) ifc.partial_inline_boxes_stack.push(partial)
}, },
InlineLevelBox::TextRun(run) => run.layout(layout_context, &mut ifc), InlineLevelBox::TextRun(run) => run.layout(layout_context, &mut ifc),
InlineLevelBox::Atomic(_independent) => { InlineLevelBox::Atomic(a) => layout_atomic(layout_context, &mut ifc, a),
// TODO
continue;
},
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => { InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => {
let initial_start_corner = let initial_start_corner =
match Display::from(box_.contents.style.get_box().original_display) { match Display::from(box_.contents.style.get_box().original_display) {
@ -131,12 +242,11 @@ impl InlineFormattingContext {
panic!("display:none does not generate an abspos box") panic!("display:none does not generate an abspos box")
}, },
}; };
absolutely_positioned_fragments ifc.absolutely_positioned_fragments
.push(box_.layout(initial_start_corner, tree_rank)); .push(box_.layout(initial_start_corner, tree_rank));
}, },
InlineLevelBox::OutOfFlowFloatBox(_box_) => { InlineLevelBox::OutOfFlowFloatBox(_box_) => {
// TODO // TODO
continue;
}, },
} }
} else } else
@ -282,12 +392,114 @@ impl<'box_tree> PartialInlineBoxFragment<'box_tree> {
} }
} }
fn layout_atomic<'box_tree>(
layout_context: &LayoutContext,
ifc: &mut InlineFormattingContextState<'box_tree, '_>,
atomic: &'box_tree IndependentFormattingContext,
) {
let cbis = ifc.containing_block.inline_size;
let padding = atomic.style.padding().percentages_relative_to(cbis);
let border = atomic.style.border_width();
let margin = atomic
.style
.margin()
.percentages_relative_to(cbis)
.auto_is(Length::zero);
let pbm = &(&padding + &border) + &margin;
ifc.inline_position += pbm.inline_start;
let mut start_corner = Vec2 {
block: pbm.block_start,
inline: ifc.inline_position - ifc.current_nesting_level.inline_start,
};
start_corner += &relative_adjustement(
&atomic.style,
ifc.containing_block.inline_size,
ifc.containing_block.block_size,
);
let fragment = match atomic.as_replaced() {
Ok(replaced) => {
// FIXME: implement https://drafts.csswg.org/css2/visudet.html#inline-replaced-width
// and https://drafts.csswg.org/css2/visudet.html#inline-replaced-height
let size = Vec2::zero();
let fragments = replaced.make_fragments(&atomic.style, size.clone());
let content_rect = Rect { start_corner, size };
BoxFragment {
style: atomic.style.clone(),
children: fragments,
content_rect,
padding,
border,
margin,
block_margins_collapsed_with_children: CollapsedBlockMargins::zero(),
}
},
Err(non_replaced) => {
let box_size = atomic.style.box_size();
let inline_size = box_size.inline.percentage_relative_to(cbis).auto_is(|| {
let available_size = cbis - pbm.inline_sum();
atomic.content_sizes.shrink_to_fit(available_size)
});
let block_size = box_size
.block
.maybe_percentage_relative_to(ifc.containing_block.block_size.non_auto());
let containing_block_for_children = ContainingBlock {
inline_size,
block_size,
mode: atomic.style.writing_mode(),
};
assert_eq!(
ifc.containing_block.mode, containing_block_for_children.mode,
"Mixed writing modes are not supported yet"
);
// FIXME is this correct?
let dummy_tree_rank = 0;
// FIXME: Do we need to call `adjust_static_positions` somewhere near here?
let independent_layout = non_replaced.layout(
layout_context,
&containing_block_for_children,
dummy_tree_rank,
ifc.absolutely_positioned_fragments,
);
let block_size = block_size.auto_is(|| independent_layout.content_block_size);
let content_rect = Rect {
start_corner,
size: Vec2 {
block: block_size,
inline: inline_size,
},
};
BoxFragment {
style: atomic.style.clone(),
children: independent_layout.fragments,
content_rect,
padding,
border,
margin,
block_margins_collapsed_with_children: CollapsedBlockMargins::zero(),
}
},
};
ifc.inline_position += pbm.inline_end;
ifc.current_nesting_level
.fragments_so_far
.push(Fragment::Box(fragment));
}
struct BreakAndShapeResult {
font_ascent: Au,
font_line_gap: Au,
font_key: FontInstanceKey,
runs: Vec<GlyphRun>,
break_at_start: bool,
}
impl TextRun { impl TextRun {
fn layout(&self, layout_context: &LayoutContext, ifc: &mut InlineFormattingContextState) { fn break_and_shape(&self, layout_context: &LayoutContext) -> BreakAndShapeResult {
use gfx::font::ShapingFlags; use gfx::font::ShapingFlags;
use style::computed_values::text_rendering::T as TextRendering; use style::computed_values::text_rendering::T as TextRendering;
use style::computed_values::word_break::T as WordBreak; use style::computed_values::word_break::T as WordBreak;
use style::values::generics::text::LineHeight;
let font_style = self.parent_style.clone_font(); let font_style = self.parent_style.clone_font();
let inherited_text_style = self.parent_style.get_inherited_text(); let inherited_text_style = self.parent_style.get_inherited_text();
@ -316,30 +528,41 @@ impl TextRun {
flags, flags,
}; };
let (font_ascent, font_line_gap, font_key, runs) = crate::context::with_thread_local_font_context(layout_context, |font_context| {
crate::context::with_thread_local_font_context(layout_context, |font_context| { let font_group = font_context.font_group(font_style);
let font_group = font_context.font_group(font_style); let font = font_group
let font = font_group .borrow_mut()
.borrow_mut() .first(font_context)
.first(font_context) .expect("could not find font");
.expect("could not find font"); let mut font = font.borrow_mut();
let mut font = font.borrow_mut();
let (runs, _break_at_start) = gfx::text::text_run::TextRun::break_and_shape( let (runs, break_at_start) = gfx::text::text_run::TextRun::break_and_shape(
&mut font, &mut font,
&self.text, &self.text,
&shaping_options, &shaping_options,
&mut None, &mut None,
); );
( BreakAndShapeResult {
font.metrics.ascent, font_ascent: font.metrics.ascent,
font.metrics.line_gap, font_line_gap: font.metrics.line_gap,
font.font_key, font_key: font.font_key,
runs, runs,
) break_at_start,
}); }
})
}
fn layout(&self, layout_context: &LayoutContext, ifc: &mut InlineFormattingContextState) {
use style::values::generics::text::LineHeight;
let BreakAndShapeResult {
font_ascent,
font_line_gap,
font_key,
runs,
break_at_start: _,
} = self.break_and_shape(layout_context);
let font_size = self.parent_style.get_font().font_size.size.0; let font_size = self.parent_style.get_font().font_size.size.0;
let mut runs = runs.iter(); let mut runs = runs.iter();
loop { loop {

View file

@ -656,17 +656,11 @@ fn layout_in_flow_replaced_block_level<'a>(
block_start: computed_margin.block_start.auto_is(Length::zero), block_start: computed_margin.block_start.auto_is(Length::zero),
block_end: computed_margin.block_end.auto_is(Length::zero), block_end: computed_margin.block_end.auto_is(Length::zero),
}; };
let containing_block_for_children = ContainingBlock { let size = Vec2 {
inline_size, block: block_size,
block_size: LengthOrAuto::LengthPercentage(block_size), inline: inline_size,
mode,
}; };
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows let fragments = replaced.make_fragments(style, size.clone());
assert_eq!(
containing_block.mode, containing_block_for_children.mode,
"Mixed writing modes are not supported yet"
);
let independent_layout = replaced.layout(style, &containing_block_for_children);
let relative_adjustement = relative_adjustement( let relative_adjustement = relative_adjustement(
style, style,
inline_size, inline_size,
@ -677,14 +671,11 @@ fn layout_in_flow_replaced_block_level<'a>(
block: pb.block_start + relative_adjustement.block, block: pb.block_start + relative_adjustement.block,
inline: pb.inline_start + relative_adjustement.inline + margin.inline_start, inline: pb.inline_start + relative_adjustement.inline + margin.inline_start,
}, },
size: Vec2 { size,
block: block_size,
inline: inline_size,
},
}; };
BoxFragment { BoxFragment {
style: style.clone(), style: style.clone(),
children: independent_layout.fragments, children: fragments,
content_rect, content_rect,
padding, padding,
border, border,

View file

@ -14,12 +14,12 @@ use crate::geom;
use crate::geom::flow_relative::Vec2; use crate::geom::flow_relative::Vec2;
use crate::positioned::AbsolutelyPositionedBox; use crate::positioned::AbsolutelyPositionedBox;
use crate::replaced::ReplacedContent; use crate::replaced::ReplacedContent;
use crate::sizing::ContentSizesRequest;
use crate::style_ext::{Direction, Display, DisplayGeneratingBox, DisplayInside, WritingMode}; use crate::style_ext::{Direction, Display, DisplayGeneratingBox, DisplayInside, WritingMode};
use crate::{ContainingBlock, DefiniteContainingBlock}; use crate::{ContainingBlock, DefiniteContainingBlock};
use rayon::iter::{IntoParallelRefIterator, ParallelExtend, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, ParallelExtend, ParallelIterator};
use script_layout_interface::wrapper_traits::LayoutNode; use script_layout_interface::wrapper_traits::LayoutNode;
use servo_arc::Arc; use servo_arc::Arc;
use style::context::SharedStyleContext;
use style::values::computed::{Length, LengthOrAuto}; use style::values::computed::{Length, LengthOrAuto};
use style::Zero; use style::Zero;
use style_traits::CSSPixel; use style_traits::CSSPixel;
@ -28,7 +28,7 @@ pub struct BoxTreeRoot(BlockFormattingContext);
pub struct FragmentTreeRoot(Vec<Fragment>); pub struct FragmentTreeRoot(Vec<Fragment>);
impl BoxTreeRoot { impl BoxTreeRoot {
pub fn construct<'dom, Node>(context: &SharedStyleContext<'_>, root_element: Node) -> Self pub fn construct<'dom, Node>(context: &LayoutContext, root_element: Node) -> Self
where where
Node: 'dom + Copy + LayoutNode + Send + Sync, Node: 'dom + Copy + LayoutNode + Send + Sync,
{ {
@ -41,7 +41,7 @@ impl BoxTreeRoot {
} }
fn construct_for_root_element<'dom>( fn construct_for_root_element<'dom>(
context: &SharedStyleContext<'_>, context: &LayoutContext,
root_element: impl NodeExt<'dom>, root_element: impl NodeExt<'dom>,
) -> (ContainsFloats, Vec<Arc<BlockLevelBox>>) { ) -> (ContainsFloats, Vec<Arc<BlockLevelBox>>) {
let style = root_element.style(context); let style = root_element.style(context);
@ -60,32 +60,33 @@ fn construct_for_root_element<'dom>(
Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside, Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside,
}; };
let position = box_style.position; let contents = replaced.map_or(Contents::OfElement(root_element), Contents::Replaced);
let float = box_style.float; if box_style.position.is_absolutely_positioned() {
let contents = IndependentFormattingContext::construct(
context,
style,
display_inside,
replaced.map_or(Contents::OfElement(root_element), Contents::Replaced),
);
if position.is_absolutely_positioned() {
( (
ContainsFloats::No, ContainsFloats::No,
vec![Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox( vec![Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
AbsolutelyPositionedBox { contents }, AbsolutelyPositionedBox::construct(context, style, display_inside, contents),
))], ))],
) )
} else if float.is_floating() { } else if box_style.float.is_floating() {
( (
ContainsFloats::Yes, ContainsFloats::Yes,
vec![Arc::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox { vec![Arc::new(BlockLevelBox::OutOfFlowFloatBox(
contents, FloatBox::construct(context, style, display_inside, contents),
}))], ))],
) )
} else { } else {
( (
ContainsFloats::No, ContainsFloats::No,
vec![Arc::new(BlockLevelBox::Independent(contents))], vec![Arc::new(BlockLevelBox::Independent(
IndependentFormattingContext::construct(
context,
style,
display_inside,
contents,
ContentSizesRequest::None,
),
))],
) )
} }
} }

View file

@ -8,11 +8,11 @@ use crate::flow::BlockFormattingContext;
use crate::fragments::Fragment; use crate::fragments::Fragment;
use crate::positioned::AbsolutelyPositionedFragment; use crate::positioned::AbsolutelyPositionedFragment;
use crate::replaced::ReplacedContent; use crate::replaced::ReplacedContent;
use crate::sizing::{BoxContentSizes, ContentSizesRequest};
use crate::style_ext::DisplayInside; use crate::style_ext::DisplayInside;
use crate::ContainingBlock; use crate::ContainingBlock;
use servo_arc::Arc; use servo_arc::Arc;
use std::convert::TryInto; use std::convert::TryInto;
use style::context::SharedStyleContext;
use style::properties::ComputedValues; use style::properties::ComputedValues;
use style::values::computed::Length; use style::values::computed::Length;
@ -20,6 +20,10 @@ use style::values::computed::Length;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct IndependentFormattingContext { pub(crate) struct IndependentFormattingContext {
pub style: Arc<ComputedValues>, pub style: Arc<ComputedValues>,
/// If it was requested during construction
pub content_sizes: BoxContentSizes,
contents: IndependentFormattingContextContents, contents: IndependentFormattingContextContents,
} }
@ -46,22 +50,39 @@ enum NonReplacedIFCKind<'a> {
} }
impl IndependentFormattingContext { impl IndependentFormattingContext {
pub fn construct<'dom, 'style>( pub fn construct<'dom>(
context: &SharedStyleContext<'style>, context: &LayoutContext,
style: Arc<ComputedValues>, style: Arc<ComputedValues>,
display_inside: DisplayInside, display_inside: DisplayInside,
contents: Contents<impl NodeExt<'dom>>, contents: Contents<impl NodeExt<'dom>>,
content_sizes: ContentSizesRequest,
) -> Self { ) -> Self {
use self::IndependentFormattingContextContents as Contents; use self::IndependentFormattingContextContents as Contents;
let contents = match contents.try_into() { let (contents, content_sizes) = match contents.try_into() {
Ok(non_replaced) => match display_inside { Ok(non_replaced) => match display_inside {
DisplayInside::Flow | DisplayInside::FlowRoot => Contents::Flow( DisplayInside::Flow | DisplayInside::FlowRoot => {
BlockFormattingContext::construct(context, &style, non_replaced), let (bfc, box_content_sizes) = BlockFormattingContext::construct(
), context,
&style,
non_replaced,
content_sizes,
);
(Contents::Flow(bfc), box_content_sizes)
},
},
Err(replaced) => {
// The `content_sizes` field is not used by layout code:
(
Contents::Replaced(replaced),
BoxContentSizes::NoneWereRequested,
)
}, },
Err(replaced) => Contents::Replaced(replaced),
}; };
Self { style, contents } Self {
style,
contents,
content_sizes,
}
} }
pub fn as_replaced(&self) -> Result<&ReplacedContent, NonReplacedIFC> { pub fn as_replaced(&self) -> Result<&ReplacedContent, NonReplacedIFC> {
@ -73,24 +94,6 @@ impl IndependentFormattingContext {
Contents::Flow(f) => Err(NR(Kind::Flow(f))), Contents::Flow(f) => Err(NR(Kind::Flow(f))),
} }
} }
pub fn layout<'a>(
&'a self,
layout_context: &LayoutContext,
containing_block: &ContainingBlock,
tree_rank: usize,
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
) -> IndependentLayout {
match self.as_replaced() {
Ok(replaced) => replaced.layout(&self.style, containing_block),
Err(ifc) => ifc.layout(
layout_context,
containing_block,
tree_rank,
absolutely_positioned_fragments,
),
}
}
} }
impl<'a> NonReplacedIFC<'a> { impl<'a> NonReplacedIFC<'a> {

View file

@ -4,6 +4,7 @@
#![deny(unsafe_code)] #![deny(unsafe_code)]
#![feature(exact_size_is_empty)] #![feature(exact_size_is_empty)]
#![feature(matches_macro)]
pub mod context; pub mod context;
pub mod data; pub mod data;
@ -18,6 +19,7 @@ mod opaque_node;
mod positioned; mod positioned;
pub mod query; pub mod query;
mod replaced; mod replaced;
mod sizing;
mod style_ext; mod style_ext;
pub mod traversal; pub mod traversal;
pub mod wrapper; pub mod wrapper;

View file

@ -3,12 +3,16 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::context::LayoutContext; use crate::context::LayoutContext;
use crate::dom_traversal::{Contents, NodeExt};
use crate::formatting_contexts::IndependentFormattingContext; use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragments::{AnonymousFragment, BoxFragment, CollapsedBlockMargins, Fragment}; use crate::fragments::{AnonymousFragment, BoxFragment, CollapsedBlockMargins, Fragment};
use crate::geom::flow_relative::{Rect, Sides, Vec2}; use crate::geom::flow_relative::{Rect, Sides, Vec2};
use crate::style_ext::{ComputedValuesExt, Direction, WritingMode}; use crate::sizing::ContentSizesRequest;
use crate::style_ext::{ComputedValuesExt, Direction, DisplayInside, WritingMode};
use crate::{ContainingBlock, DefiniteContainingBlock}; use crate::{ContainingBlock, DefiniteContainingBlock};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use servo_arc::Arc;
use style::properties::ComputedValues;
use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
use style::Zero; use style::Zero;
@ -42,6 +46,32 @@ pub(crate) enum AbsoluteBoxOffsets<NonStatic> {
} }
impl AbsolutelyPositionedBox { impl AbsolutelyPositionedBox {
pub fn construct<'dom>(
context: &LayoutContext,
style: Arc<ComputedValues>,
display_inside: DisplayInside,
contents: Contents<impl NodeExt<'dom>>,
) -> Self {
// "Shrink-to-fit" in https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width
let content_sizes = ContentSizesRequest::inline_if(
// If inline-size is non-auto, that value is used without shrink-to-fit
style.inline_size_is_auto() &&
// If it is, then the only case where shrink-to-fit is *not* used is
// if both offsets are non-auto, leaving inline-size as the only variable
// in the constraint equation.
!style.inline_box_offsets_are_both_non_auto(),
);
Self {
contents: IndependentFormattingContext::construct(
context,
style,
display_inside,
contents,
content_sizes,
),
}
}
pub(crate) fn layout<'a>( pub(crate) fn layout<'a>(
&'a self, &'a self,
initial_start_corner: Vec2<Length>, initial_start_corner: Vec2<Length>,
@ -256,28 +286,60 @@ impl<'a> AbsolutelyPositionedFragment<'a> {
Anchor::End(end) => cbis - end - pb.inline_sum() - margin.inline_sum(), Anchor::End(end) => cbis - end - pb.inline_sum() - margin.inline_sum(),
}; };
// FIXME(nox): shrink-to-fit. if self
available_size .absolutely_positioned_box
.contents
.as_replaced()
.is_ok()
{
// FIXME: implement https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
available_size
} else {
self.absolutely_positioned_box
.contents
.content_sizes
.shrink_to_fit(available_size)
}
}); });
let containing_block_for_children = ContainingBlock { let mut absolutely_positioned_fragments = Vec::new();
inline_size, let mut independent_layout = match self.absolutely_positioned_box.contents.as_replaced() {
block_size, Ok(replaced) => {
mode: style.writing_mode(), // FIXME: implement https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
// and https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
let block_size = block_size.auto_is(Length::zero);
let fragments = replaced.make_fragments(
&self.absolutely_positioned_box.contents.style,
Vec2 {
inline: inline_size,
block: block_size,
},
);
crate::formatting_contexts::IndependentLayout {
fragments,
content_block_size: block_size,
}
},
Err(non_replaced) => {
let containing_block_for_children = ContainingBlock {
inline_size,
block_size,
mode: style.writing_mode(),
};
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
assert_eq!(
containing_block.mode, containing_block_for_children.mode,
"Mixed writing modes are not supported yet"
);
let dummy_tree_rank = 0;
non_replaced.layout(
layout_context,
&containing_block_for_children,
dummy_tree_rank,
&mut absolutely_positioned_fragments,
)
},
}; };
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
assert_eq!(
containing_block.mode, containing_block_for_children.mode,
"Mixed writing modes are not supported yet"
);
let dummy_tree_rank = 0;
let mut absolutely_positioned_fragments = vec![];
let mut independent_layout = self.absolutely_positioned_box.contents.layout(
layout_context,
&containing_block_for_children,
dummy_tree_rank,
&mut absolutely_positioned_fragments,
);
let inline_start = match inline_anchor { let inline_start = match inline_anchor {
Anchor::Start(start) => start + pb.inline_start + margin.inline_start, Anchor::Start(start) => start + pb.inline_start + margin.inline_start,
@ -307,7 +369,7 @@ impl<'a> AbsolutelyPositionedFragment<'a> {
&mut independent_layout.fragments, &mut independent_layout.fragments,
&content_rect.size, &content_rect.size,
&padding, &padding,
containing_block_for_children.mode, style.writing_mode(),
); );
Fragment::Box(BoxFragment { Fragment::Box(BoxFragment {

View file

@ -3,10 +3,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::dom_traversal::NodeExt; use crate::dom_traversal::NodeExt;
use crate::formatting_contexts::IndependentLayout;
use crate::fragments::{Fragment, ImageFragment}; use crate::fragments::{Fragment, ImageFragment};
use crate::geom::{flow_relative, physical}; use crate::geom::{flow_relative, physical};
use crate::ContainingBlock;
use net_traits::image::base::Image; use net_traits::image::base::Image;
use servo_arc::Arc as ServoArc; use servo_arc::Arc as ServoArc;
use std::sync::Arc; use std::sync::Arc;
@ -35,39 +33,27 @@ impl ReplacedContent {
None None
} }
pub fn layout<'a>( pub fn make_fragments<'a>(
&'a self, &'a self,
style: &ServoArc<ComputedValues>, style: &ServoArc<ComputedValues>,
containing_block: &ContainingBlock, size: flow_relative::Vec2<Length>,
) -> IndependentLayout { ) -> Vec<Fragment> {
let (fragments, content_block_size) = match self.kind { match &self.kind {
ReplacedContentKind::Image(ref image) => { ReplacedContentKind::Image(image) => image
// FIXME(nox): We should not assume block size is known. .as_ref()
let block_size = containing_block.block_size.non_auto().unwrap(); .and_then(|image| image.id)
let fragments = image .map(|image_key| {
.as_ref() Fragment::Image(ImageFragment {
.and_then(|image| image.id) style: style.clone(),
.map(|image_key| { content_rect: flow_relative::Rect {
Fragment::Image(ImageFragment { start_corner: flow_relative::Vec2::zero(),
style: style.clone(), size,
content_rect: flow_relative::Rect { },
start_corner: flow_relative::Vec2::zero(), image_key,
size: flow_relative::Vec2 {
inline: containing_block.inline_size,
block: block_size,
},
},
image_key,
})
}) })
.into_iter() })
.collect::<Vec<_>>(); .into_iter()
(fragments, block_size) .collect(),
},
};
IndependentLayout {
fragments,
content_block_size,
} }
} }
} }

View file

@ -0,0 +1,152 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! https://drafts.csswg.org/css-sizing/
use crate::style_ext::ComputedValuesExt;
use style::properties::ComputedValues;
use style::values::computed::{Length, LengthPercentage, Percentage};
use style::Zero;
/// Which min/max-content values should be computed during box construction
#[derive(Clone, Copy, Debug)]
pub(crate) enum ContentSizesRequest {
Inline,
None,
}
impl ContentSizesRequest {
pub fn inline_if(condition: bool) -> Self {
if condition {
Self::Inline
} else {
Self::None
}
}
pub fn requests_inline(self) -> bool {
match self {
Self::Inline => true,
Self::None => false,
}
}
pub fn if_requests_inline<T>(self, f: impl FnOnce() -> T) -> Option<T> {
match self {
Self::Inline => Some(f()),
Self::None => None,
}
}
pub fn compute(self, compute_inline: impl FnOnce() -> ContentSizes) -> BoxContentSizes {
match self {
Self::Inline => BoxContentSizes::Inline(compute_inline()),
Self::None => BoxContentSizes::NoneWereRequested,
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct ContentSizes {
pub min_content: Length,
pub max_content: Length,
}
/// https://drafts.csswg.org/css-sizing/#intrinsic-sizes
impl ContentSizes {
pub fn zero() -> Self {
Self {
min_content: Length::zero(),
max_content: Length::zero(),
}
}
pub fn max_assign(&mut self, other: &Self) {
self.min_content.max_assign(other.min_content);
self.max_content.max_assign(other.max_content);
}
/// Relevant to outer intrinsic inline sizes, for percentages from padding and margin.
pub fn adjust_for_pbm_percentages(&mut self, percentages: Percentage) {
// " Note that this may yield an infinite result, but undefined results
// (zero divided by zero) must be treated as zero. "
if self.max_content.px() == 0. {
// Avoid a potential `NaN`.
// Zero is already the result we want regardless of `denominator`.
} else {
let denominator = (1. - percentages.0).max(0.);
self.max_content = Length::new(self.max_content.px() / denominator);
}
}
}
/// Optional min/max-content for storage in the box tree
#[derive(Debug)]
pub(crate) enum BoxContentSizes {
NoneWereRequested, // … during box construction
Inline(ContentSizes),
}
impl BoxContentSizes {
fn expect_inline(&self) -> &ContentSizes {
match self {
Self::NoneWereRequested => panic!("Accessing content size that was not requested"),
Self::Inline(s) => s,
}
}
/// https://dbaron.org/css/intrinsic/#outer-intrinsic
pub fn outer_inline(&self, style: &ComputedValues) -> ContentSizes {
let (mut outer, percentages) = self.outer_inline_and_percentages(style);
outer.adjust_for_pbm_percentages(percentages);
outer
}
pub(crate) fn outer_inline_and_percentages(
&self,
style: &ComputedValues,
) -> (ContentSizes, Percentage) {
// FIXME: account for 'min-width', 'max-width', 'box-sizing'
let inline_size = style.box_size().inline;
// Percentages for 'width' are treated as 'auto'
let inline_size = inline_size.map(|lp| lp.as_length());
// The (inner) min/max-content are only used for 'auto'
let mut outer = match inline_size.non_auto().flatten() {
None => self.expect_inline().clone(),
Some(length) => ContentSizes {
min_content: length,
max_content: length,
},
};
let mut pbm_lengths = Length::zero();
let mut pbm_percentages = Percentage::zero();
let padding = style.padding();
let border = style.border_width();
let margin = style.margin();
pbm_lengths += border.inline_sum();
let mut add = |x: LengthPercentage| {
pbm_lengths += x.length_component();
pbm_percentages += x.percentage_component();
};
add(padding.inline_start);
add(padding.inline_end);
margin.inline_start.non_auto().map(&mut add);
margin.inline_end.non_auto().map(&mut add);
outer.min_content += pbm_lengths;
outer.max_content += pbm_lengths;
(outer, pbm_percentages)
}
/// https://drafts.csswg.org/css2/visudet.html#shrink-to-fit-float
pub(crate) fn shrink_to_fit(&self, available_size: Length) -> Length {
let inline = self.expect_inline();
available_size
.max(inline.min_content)
.min(inline.max_content)
}
}

View file

@ -45,6 +45,9 @@ pub(crate) enum DisplayInside {
pub(crate) trait ComputedValuesExt { pub(crate) trait ComputedValuesExt {
fn writing_mode(&self) -> (WritingMode, Direction); fn writing_mode(&self) -> (WritingMode, Direction);
fn writing_mode_is_horizontal(&self) -> bool;
fn inline_size_is_auto(&self) -> bool;
fn inline_box_offsets_are_both_non_auto(&self) -> bool;
fn box_offsets(&self) -> flow_relative::Sides<LengthPercentageOrAuto>; fn box_offsets(&self) -> flow_relative::Sides<LengthPercentageOrAuto>;
fn box_size(&self) -> flow_relative::Vec2<LengthPercentageOrAuto>; fn box_size(&self) -> flow_relative::Vec2<LengthPercentageOrAuto>;
fn min_box_size(&self) -> flow_relative::Vec2<LengthPercentageOrAuto>; fn min_box_size(&self) -> flow_relative::Vec2<LengthPercentageOrAuto>;
@ -62,6 +65,39 @@ impl ComputedValuesExt for ComputedValues {
(writing_mode, direction) (writing_mode, direction)
} }
fn writing_mode_is_horizontal(&self) -> bool {
match self.get_inherited_box().writing_mode {
WritingMode::HorizontalTb => true,
WritingMode::VerticalLr | WritingMode::VerticalRl => false,
}
}
fn inline_size_is_auto(&self) -> bool {
let position = self.get_position();
let size = if self.writing_mode_is_horizontal() {
position.width
} else {
position.height
};
matches!(size, Size::Auto)
}
fn inline_box_offsets_are_both_non_auto(&self) -> bool {
let position = self.get_position();
let offsets = if self.writing_mode_is_horizontal() {
(position.left, position.right)
} else {
(position.top, position.bottom)
};
matches!(
offsets,
(
LengthPercentageOrAuto::LengthPercentage(_),
LengthPercentageOrAuto::LengthPercentage(_),
)
)
}
#[inline] #[inline]
fn box_offsets(&self) -> flow_relative::Sides<LengthPercentageOrAuto> { fn box_offsets(&self) -> flow_relative::Sides<LengthPercentageOrAuto> {
let position = self.get_position(); let position = self.get_position();

View file

@ -20,6 +20,10 @@ impl<'a> RecalcStyle<'a> {
RecalcStyle { context: context } RecalcStyle { context: context }
} }
pub fn context(&self) -> &LayoutContext<'a> {
&self.context
}
pub fn destroy(self) -> LayoutContext<'a> { pub fn destroy(self) -> LayoutContext<'a> {
self.context self.context
} }

View file

@ -1081,9 +1081,9 @@ impl LayoutThread {
let box_tree = if token.should_traverse() { let box_tree = if token.should_traverse() {
driver::traverse_dom(&traversal, token, Some(rayon_pool)); driver::traverse_dom(&traversal, token, Some(rayon_pool));
let shared = DomTraversal::<ServoLayoutElement>::shared_context(&traversal);
let root_node = document.root_element().unwrap().as_node(); let root_node = document.root_element().unwrap().as_node();
let box_tree = rayon_pool.install(|| BoxTreeRoot::construct(shared, root_node)); let box_tree =
rayon_pool.install(|| BoxTreeRoot::construct(traversal.context(), root_node));
Some(box_tree) Some(box_tree)
} else { } else {
None None

View file

@ -170,6 +170,12 @@ impl LengthPercentage {
self.length self.length
} }
/// Returns the percentage component of this `calc()`
#[inline]
pub fn percentage_component(&self) -> Percentage {
Percentage(self.clamping_mode.clamp(self.percentage.0))
}
/// Return the percentage value as CSSFloat. /// Return the percentage value as CSSFloat.
#[inline] #[inline]
pub fn percentage(&self) -> CSSFloat { pub fn percentage(&self) -> CSSFloat {
@ -186,6 +192,16 @@ impl LengthPercentage {
} }
} }
/// Returns the length component if this could be represented as a
/// non-calc length.
pub fn as_length(&self) -> Option<Length> {
if !self.has_percentage {
Some(self.length_component())
} else {
None
}
}
/// Returns the percentage component if this could be represented as a /// Returns the percentage component if this could be represented as a
/// non-calc percentage. /// non-calc percentage.
pub fn as_percentage(&self) -> Option<Percentage> { pub fn as_percentage(&self) -> Option<Percentage> {

View file

@ -64,6 +64,12 @@ impl Zero for Percentage {
} }
} }
impl std::ops::AddAssign for Percentage {
fn add_assign(&mut self, other: Self) {
self.0 += other.0
}
}
impl ToCss for Percentage { impl ToCss for Percentage {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where where

View file

@ -92,7 +92,7 @@ where
} }
/// Maps the length of this value. /// Maps the length of this value.
pub fn map(&self, f: impl FnOnce(LengthPercentage) -> LengthPercentage) -> Self { pub fn map<T>(&self, f: impl FnOnce(LengthPercentage) -> T) -> LengthPercentageOrAuto<T> {
match self { match self {
LengthPercentageOrAuto::LengthPercentage(l) => { LengthPercentageOrAuto::LengthPercentage(l) => {
LengthPercentageOrAuto::LengthPercentage(f(l.clone())) LengthPercentageOrAuto::LengthPercentage(f(l.clone()))

View file

@ -1,2 +0,0 @@
[containing-block-008.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[containing-block-010.xht]
expected: FAIL