mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Auto merge of #25033 - servo:intrinsic, r=nox
Add support for inline-block and for computing min/max-content
This commit is contained in:
commit
e70397d90a
19 changed files with 823 additions and 272 deletions
|
@ -2,6 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
use crate::context::LayoutContext;
|
||||
use crate::element_data::{LayoutBox, LayoutDataForElement};
|
||||
use crate::geom::physical::Vec2;
|
||||
use crate::replaced::ReplacedContent;
|
||||
|
@ -13,7 +14,6 @@ use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
|
|||
use servo_arc::Arc as ServoArc;
|
||||
use std::marker::PhantomData as marker;
|
||||
use std::sync::Arc;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::dom::TNode;
|
||||
use style::properties::ComputedValues;
|
||||
use style::selector_parser::PseudoElement;
|
||||
|
@ -66,7 +66,7 @@ where
|
|||
|
||||
fn traverse_children_of<'dom, Node>(
|
||||
parent_element: Node,
|
||||
context: &SharedStyleContext,
|
||||
context: &LayoutContext,
|
||||
handler: &mut impl TraversalHandler<'dom, Node>,
|
||||
) where
|
||||
Node: NodeExt<'dom>,
|
||||
|
@ -88,7 +88,7 @@ fn traverse_children_of<'dom, Node>(
|
|||
|
||||
fn traverse_element<'dom, Node>(
|
||||
element: Node,
|
||||
context: &SharedStyleContext,
|
||||
context: &LayoutContext,
|
||||
handler: &mut impl TraversalHandler<'dom, Node>,
|
||||
) where
|
||||
Node: NodeExt<'dom>,
|
||||
|
@ -121,7 +121,7 @@ fn traverse_element<'dom, Node>(
|
|||
fn traverse_pseudo_element<'dom, Node>(
|
||||
which: WhichPseudoElement,
|
||||
element: Node,
|
||||
context: &SharedStyleContext,
|
||||
context: &LayoutContext,
|
||||
handler: &mut impl TraversalHandler<'dom, Node>,
|
||||
) where
|
||||
Node: NodeExt<'dom>,
|
||||
|
@ -146,7 +146,7 @@ fn traverse_pseudo_element<'dom, Node>(
|
|||
|
||||
fn traverse_pseudo_element_contents<'dom, Node>(
|
||||
pseudo_element_style: &ServoArc<ComputedValues>,
|
||||
context: &SharedStyleContext,
|
||||
context: &LayoutContext,
|
||||
handler: &mut impl TraversalHandler<'dom, Node>,
|
||||
items: Vec<PseudoElementContentItem>,
|
||||
) where
|
||||
|
@ -159,9 +159,10 @@ fn traverse_pseudo_element_contents<'dom, Node>(
|
|||
PseudoElementContentItem::Replaced(contents) => {
|
||||
let item_style = anonymous_style.get_or_insert_with(|| {
|
||||
context
|
||||
.shared_context()
|
||||
.stylist
|
||||
.style_for_anonymous::<Node::ConcreteElement>(
|
||||
&context.guards,
|
||||
&context.shared_context().guards,
|
||||
&PseudoElement::ServoText,
|
||||
&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> {
|
||||
type Error = ReplacedContent;
|
||||
|
||||
|
@ -215,7 +226,7 @@ where
|
|||
pub(crate) fn traverse(
|
||||
self,
|
||||
inherited_style: &ServoArc<ComputedValues>,
|
||||
context: &SharedStyleContext,
|
||||
context: &LayoutContext,
|
||||
handler: &mut impl TraversalHandler<'dom, Node>,
|
||||
) {
|
||||
match self {
|
||||
|
@ -230,7 +241,7 @@ where
|
|||
fn pseudo_element_style<'dom, Node>(
|
||||
_which: WhichPseudoElement,
|
||||
_element: Node,
|
||||
_context: &SharedStyleContext,
|
||||
_context: &LayoutContext,
|
||||
) -> Option<ServoArc<ComputedValues>>
|
||||
where
|
||||
Node: NodeExt<'dom>,
|
||||
|
@ -243,7 +254,7 @@ where
|
|||
fn generate_pseudo_element_content<'dom, Node>(
|
||||
_pseudo_element_style: &ComputedValues,
|
||||
_element: Node,
|
||||
_context: &SharedStyleContext,
|
||||
_context: &LayoutContext,
|
||||
) -> Vec<PseudoElementContentItem>
|
||||
where
|
||||
Node: NodeExt<'dom>,
|
||||
|
@ -292,7 +303,7 @@ pub(crate) trait NodeExt<'dom>: 'dom + Copy + LayoutNode + Send + Sync {
|
|||
fn first_child(self) -> Option<Self>;
|
||||
fn next_sibling(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 element_box_slot(&self) -> BoxSlot<'dom>;
|
||||
|
@ -349,8 +360,8 @@ where
|
|||
TNode::parent_node(&self)
|
||||
}
|
||||
|
||||
fn style(self, context: &SharedStyleContext) -> ServoArc<ComputedValues> {
|
||||
self.to_threadsafe().style(context)
|
||||
fn style(self, context: &LayoutContext) -> ServoArc<ComputedValues> {
|
||||
self.to_threadsafe().style(context.shared_context())
|
||||
}
|
||||
|
||||
fn layout_data_mut(&self) -> AtomicRefMut<LayoutDataForElement> {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
use crate::context::LayoutContext;
|
||||
use crate::dom_traversal::{BoxSlot, Contents, NodeExt, NonReplacedContents, TraversalHandler};
|
||||
use crate::element_data::LayoutBox;
|
||||
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::formatting_contexts::IndependentFormattingContext;
|
||||
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_croissant::ParallelIteratorExt;
|
||||
use servo_arc::Arc;
|
||||
use std::convert::TryInto;
|
||||
use style::context::SharedStyleContext;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use style::properties::ComputedValues;
|
||||
use style::selector_parser::PseudoElement;
|
||||
|
||||
impl BlockFormattingContext {
|
||||
pub fn construct<'dom>(
|
||||
context: &SharedStyleContext<'_>,
|
||||
context: &LayoutContext,
|
||||
style: &Arc<ComputedValues>,
|
||||
contents: NonReplacedContents<impl NodeExt<'dom>>,
|
||||
) -> Self {
|
||||
let (contents, contains_floats) = BlockContainer::construct(context, style, contents);
|
||||
Self {
|
||||
content_sizes: ContentSizesRequest,
|
||||
) -> (Self, BoxContentSizes) {
|
||||
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,
|
||||
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
|
||||
/// and does a preorder traversal of all of its inclusive siblings.
|
||||
struct BlockContainerBuilder<'dom, 'style, Node> {
|
||||
context: &'style SharedStyleContext<'style>,
|
||||
context: &'style LayoutContext<'style>,
|
||||
|
||||
block_container_style: &'style Arc<ComputedValues>,
|
||||
|
||||
|
@ -123,19 +129,20 @@ struct BlockContainerBuilder<'dom, 'style, Node> {
|
|||
}
|
||||
|
||||
impl BlockContainer {
|
||||
pub fn construct<'dom, 'style>(
|
||||
context: &SharedStyleContext<'style>,
|
||||
pub fn construct<'dom>(
|
||||
context: &LayoutContext,
|
||||
block_container_style: &Arc<ComputedValues>,
|
||||
contents: NonReplacedContents<impl NodeExt<'dom>>,
|
||||
) -> (BlockContainer, ContainsFloats) {
|
||||
content_sizes: ContentSizesRequest,
|
||||
) -> (BlockContainer, ContainsFloats, BoxContentSizes) {
|
||||
let mut builder = BlockContainerBuilder {
|
||||
context,
|
||||
block_container_style,
|
||||
block_level_boxes: Default::default(),
|
||||
ongoing_inline_formatting_context: Default::default(),
|
||||
ongoing_inline_boxes_stack: Default::default(),
|
||||
anonymous_style: Default::default(),
|
||||
contains_floats: Default::default(),
|
||||
block_level_boxes: Vec::new(),
|
||||
ongoing_inline_formatting_context: InlineFormattingContext::default(),
|
||||
ongoing_inline_boxes_stack: Vec::new(),
|
||||
anonymous_style: None,
|
||||
contains_floats: ContainsFloats::No,
|
||||
};
|
||||
|
||||
contents.traverse(block_container_style, context, &mut builder);
|
||||
|
@ -148,32 +155,65 @@ impl BlockContainer {
|
|||
.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(
|
||||
builder.ongoing_inline_formatting_context,
|
||||
);
|
||||
return (container, builder.contains_floats);
|
||||
return (container, builder.contains_floats, content_sizes);
|
||||
}
|
||||
builder.end_ongoing_inline_formatting_context();
|
||||
}
|
||||
|
||||
let mut contains_floats = builder.contains_floats;
|
||||
let container = BlockContainer::BlockLevelBoxes(
|
||||
builder
|
||||
.block_level_boxes
|
||||
.into_par_iter()
|
||||
.mapfold_reduce_into(
|
||||
&mut contains_floats,
|
||||
|contains_floats, (intermediate, box_slot): (IntermediateBlockLevelBox<_>, BoxSlot<'_>)| {
|
||||
let (block_level_box, box_contains_floats) = intermediate.finish(context);
|
||||
*contains_floats |= box_contains_floats;
|
||||
type Intermediate<Node> = IntermediateBlockLevelBox<Node>;
|
||||
struct Target {
|
||||
contains_floats: ContainsFloats,
|
||||
outer_content_sizes_of_children: ContentSizes,
|
||||
}
|
||||
impl Default for Target {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
contains_floats: ContainsFloats::No,
|
||||
outer_content_sizes_of_children: ContentSizes::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut target = Target {
|
||||
contains_floats: builder.contains_floats,
|
||||
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 |= right,
|
||||
)
|
||||
.collect(),
|
||||
|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,21 +364,10 @@ where
|
|||
display_inside: DisplayInside,
|
||||
contents: Contents<Node>,
|
||||
) -> Arc<InlineLevelBox> {
|
||||
let box_ = match contents.try_into() {
|
||||
Err(replaced) => Arc::new(InlineLevelBox::Atomic(
|
||||
IndependentFormattingContext::construct(
|
||||
self.context,
|
||||
style.clone(),
|
||||
display_inside,
|
||||
<Contents<Node>>::Replaced(replaced),
|
||||
),
|
||||
)),
|
||||
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.
|
||||
let box_ = if display_inside == DisplayInside::Flow && !contents.is_replaced() {
|
||||
// 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,
|
||||
|
@ -346,7 +375,10 @@ where
|
|||
children: vec![],
|
||||
});
|
||||
|
||||
NonReplacedContents::traverse(non_replaced, &style, self.context, self);
|
||||
// `unwrap` doesn’t 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
|
||||
|
@ -354,8 +386,16 @@ where
|
|||
.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(
|
||||
self.context,
|
||||
style.clone(),
|
||||
display_inside,
|
||||
contents,
|
||||
ContentSizesRequest::inline_if(style.inline_size_is_auto()),
|
||||
),
|
||||
))
|
||||
};
|
||||
self.current_inline_level_boxes().push(box_.clone());
|
||||
box_
|
||||
|
@ -451,14 +491,7 @@ where
|
|||
self.block_level_boxes.push((box_, box_slot));
|
||||
} else {
|
||||
let box_ = Arc::new(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(
|
||||
AbsolutelyPositionedBox {
|
||||
contents: IndependentFormattingContext::construct(
|
||||
self.context,
|
||||
style,
|
||||
display_inside,
|
||||
contents,
|
||||
),
|
||||
},
|
||||
AbsolutelyPositionedBox::construct(self.context, style, display_inside, contents),
|
||||
));
|
||||
self.current_inline_level_boxes().push(box_.clone());
|
||||
box_slot.set(LayoutBox::InlineLevel(box_))
|
||||
|
@ -482,14 +515,12 @@ where
|
|||
};
|
||||
self.block_level_boxes.push((box_, box_slot));
|
||||
} else {
|
||||
let box_ = Arc::new(InlineLevelBox::OutOfFlowFloatBox(FloatBox {
|
||||
contents: IndependentFormattingContext::construct(
|
||||
let box_ = Arc::new(InlineLevelBox::OutOfFlowFloatBox(FloatBox::construct(
|
||||
self.context,
|
||||
style,
|
||||
display_inside,
|
||||
contents,
|
||||
),
|
||||
}));
|
||||
)));
|
||||
self.current_inline_level_boxes().push(box_.clone());
|
||||
box_slot.set(LayoutBox::InlineLevel(box_))
|
||||
}
|
||||
|
@ -509,9 +540,10 @@ where
|
|||
let block_container_style = self.block_container_style;
|
||||
let anonymous_style = self.anonymous_style.get_or_insert_with(|| {
|
||||
context
|
||||
.shared_context()
|
||||
.stylist
|
||||
.style_for_anonymous::<Node::ConcreteElement>(
|
||||
&context.guards,
|
||||
&context.shared_context().guards,
|
||||
&PseudoElement::ServoText,
|
||||
&block_container_style,
|
||||
)
|
||||
|
@ -546,13 +578,24 @@ impl<'dom, Node> IntermediateBlockLevelBox<Node>
|
|||
where
|
||||
Node: NodeExt<'dom>,
|
||||
{
|
||||
fn finish<'style>(
|
||||
fn finish(
|
||||
self,
|
||||
context: &SharedStyleContext<'style>,
|
||||
context: &LayoutContext,
|
||||
max_assign_in_flow_outer_content_sizes_to: Option<&mut ContentSizes>,
|
||||
) -> (Arc<BlockLevelBox>, ContainsFloats) {
|
||||
match self {
|
||||
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 =
|
||||
Arc::new(BlockLevelBox::SameFormattingContextBlock { contents, style });
|
||||
(block_level_box, contains_floats)
|
||||
|
@ -562,12 +605,20 @@ where
|
|||
display_inside,
|
||||
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(
|
||||
context,
|
||||
style,
|
||||
display_inside,
|
||||
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)),
|
||||
ContainsFloats::No,
|
||||
|
@ -579,14 +630,7 @@ where
|
|||
contents,
|
||||
} => {
|
||||
let block_level_box = Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
|
||||
AbsolutelyPositionedBox {
|
||||
contents: IndependentFormattingContext::construct(
|
||||
context,
|
||||
style,
|
||||
display_inside,
|
||||
contents,
|
||||
),
|
||||
},
|
||||
AbsolutelyPositionedBox::construct(context, style, display_inside, contents),
|
||||
));
|
||||
(block_level_box, ContainsFloats::No)
|
||||
},
|
||||
|
@ -595,14 +639,9 @@ where
|
|||
display_inside,
|
||||
contents,
|
||||
} => {
|
||||
let contents = IndependentFormattingContext::construct(
|
||||
context,
|
||||
style,
|
||||
display_inside,
|
||||
contents,
|
||||
);
|
||||
let block_level_box =
|
||||
Arc::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox { contents }));
|
||||
let block_level_box = Arc::new(BlockLevelBox::OutOfFlowFloatBox(
|
||||
FloatBox::construct(context, style, display_inside, contents),
|
||||
));
|
||||
(block_level_box, ContainsFloats::Yes)
|
||||
},
|
||||
}
|
||||
|
@ -613,22 +652,25 @@ impl<'dom, Node> IntermediateBlockContainer<Node>
|
|||
where
|
||||
Node: NodeExt<'dom>,
|
||||
{
|
||||
fn finish<'style>(
|
||||
fn finish(
|
||||
self,
|
||||
context: &SharedStyleContext<'style>,
|
||||
context: &LayoutContext,
|
||||
style: &Arc<ComputedValues>,
|
||||
) -> (BlockContainer, ContainsFloats) {
|
||||
content_sizes: ContentSizesRequest,
|
||||
) -> (BlockContainer, ContainsFloats, BoxContentSizes) {
|
||||
match self {
|
||||
IntermediateBlockContainer::Deferred { contents } => {
|
||||
BlockContainer::construct(context, style, contents)
|
||||
BlockContainer::construct(context, style, contents, content_sizes)
|
||||
},
|
||||
IntermediateBlockContainer::InlineFormattingContext(ifc) => {
|
||||
let content_sizes = content_sizes.compute(|| ifc.inline_content_sizes(context));
|
||||
// If that inline formatting context contained any float, those
|
||||
// were already taken into account during the first phase of
|
||||
// box construction.
|
||||
(
|
||||
BlockContainer::InlineFormattingContext(ifc),
|
||||
ContainsFloats::No,
|
||||
content_sizes,
|
||||
)
|
||||
},
|
||||
}
|
||||
|
@ -648,9 +690,3 @@ impl std::ops::BitOrAssign for ContainsFloats {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ContainsFloats {
|
||||
fn default() -> Self {
|
||||
ContainsFloats::No
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,13 @@
|
|||
* 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/. */
|
||||
|
||||
use crate::context::LayoutContext;
|
||||
use crate::dom_traversal::{Contents, NodeExt};
|
||||
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)]
|
||||
pub(crate) struct FloatBox {
|
||||
|
@ -19,3 +25,23 @@ impl 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,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,16 @@ use crate::fragments::CollapsedBlockMargins;
|
|||
use crate::fragments::{AnonymousFragment, BoxFragment, Fragment, TextFragment};
|
||||
use crate::geom::flow_relative::{Rect, Sides, Vec2};
|
||||
use crate::positioned::{AbsolutelyPositionedBox, AbsolutelyPositionedFragment};
|
||||
use crate::sizing::ContentSizes;
|
||||
use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayOutside};
|
||||
use crate::{relative_adjustement, ContainingBlock};
|
||||
use app_units::Au;
|
||||
use gfx::text::text_run::GlyphRun;
|
||||
use servo_arc::Arc;
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::Length;
|
||||
use style::values::computed::{Length, LengthPercentage, Percentage};
|
||||
use style::Zero;
|
||||
use webrender_api::FontInstanceKey;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct InlineFormattingContext {
|
||||
|
@ -63,8 +67,9 @@ struct PartialInlineBoxFragment<'box_tree> {
|
|||
parent_nesting_level: InlineNestingLevelState<'box_tree>,
|
||||
}
|
||||
|
||||
struct InlineFormattingContextState<'box_tree, 'cb> {
|
||||
containing_block: &'cb ContainingBlock,
|
||||
struct InlineFormattingContextState<'box_tree, 'a> {
|
||||
absolutely_positioned_fragments: &'a mut Vec<AbsolutelyPositionedFragment<'box_tree>>,
|
||||
containing_block: &'a ContainingBlock,
|
||||
line_boxes: LinesBoxes,
|
||||
inline_position: Length,
|
||||
partial_inline_boxes_stack: Vec<PartialInlineBoxFragment<'box_tree>>,
|
||||
|
@ -77,6 +82,114 @@ struct LinesBoxes {
|
|||
}
|
||||
|
||||
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>(
|
||||
&'a self,
|
||||
layout_context: &LayoutContext,
|
||||
|
@ -85,6 +198,7 @@ impl InlineFormattingContext {
|
|||
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
|
||||
) -> FlowLayout {
|
||||
let mut ifc = InlineFormattingContextState {
|
||||
absolutely_positioned_fragments,
|
||||
containing_block,
|
||||
partial_inline_boxes_stack: Vec::new(),
|
||||
line_boxes: LinesBoxes {
|
||||
|
@ -107,10 +221,7 @@ impl InlineFormattingContext {
|
|||
ifc.partial_inline_boxes_stack.push(partial)
|
||||
},
|
||||
InlineLevelBox::TextRun(run) => run.layout(layout_context, &mut ifc),
|
||||
InlineLevelBox::Atomic(_independent) => {
|
||||
// TODO
|
||||
continue;
|
||||
},
|
||||
InlineLevelBox::Atomic(a) => layout_atomic(layout_context, &mut ifc, a),
|
||||
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => {
|
||||
let initial_start_corner =
|
||||
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")
|
||||
},
|
||||
};
|
||||
absolutely_positioned_fragments
|
||||
ifc.absolutely_positioned_fragments
|
||||
.push(box_.layout(initial_start_corner, tree_rank));
|
||||
},
|
||||
InlineLevelBox::OutOfFlowFloatBox(_box_) => {
|
||||
// TODO
|
||||
continue;
|
||||
},
|
||||
}
|
||||
} 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 {
|
||||
fn layout(&self, layout_context: &LayoutContext, ifc: &mut InlineFormattingContextState) {
|
||||
fn break_and_shape(&self, layout_context: &LayoutContext) -> BreakAndShapeResult {
|
||||
use gfx::font::ShapingFlags;
|
||||
use style::computed_values::text_rendering::T as TextRendering;
|
||||
use style::computed_values::word_break::T as WordBreak;
|
||||
use style::values::generics::text::LineHeight;
|
||||
|
||||
let font_style = self.parent_style.clone_font();
|
||||
let inherited_text_style = self.parent_style.get_inherited_text();
|
||||
|
@ -316,7 +528,6 @@ impl TextRun {
|
|||
flags,
|
||||
};
|
||||
|
||||
let (font_ascent, font_line_gap, font_key, runs) =
|
||||
crate::context::with_thread_local_font_context(layout_context, |font_context| {
|
||||
let font_group = font_context.font_group(font_style);
|
||||
let font = font_group
|
||||
|
@ -325,21 +536,33 @@ impl TextRun {
|
|||
.expect("could not find font");
|
||||
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,
|
||||
&self.text,
|
||||
&shaping_options,
|
||||
&mut None,
|
||||
);
|
||||
|
||||
(
|
||||
font.metrics.ascent,
|
||||
font.metrics.line_gap,
|
||||
font.font_key,
|
||||
BreakAndShapeResult {
|
||||
font_ascent: font.metrics.ascent,
|
||||
font_line_gap: font.metrics.line_gap,
|
||||
font_key: font.font_key,
|
||||
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 mut runs = runs.iter();
|
||||
loop {
|
||||
|
|
|
@ -656,17 +656,11 @@ fn layout_in_flow_replaced_block_level<'a>(
|
|||
block_start: computed_margin.block_start.auto_is(Length::zero),
|
||||
block_end: computed_margin.block_end.auto_is(Length::zero),
|
||||
};
|
||||
let containing_block_for_children = ContainingBlock {
|
||||
inline_size,
|
||||
block_size: LengthOrAuto::LengthPercentage(block_size),
|
||||
mode,
|
||||
let size = Vec2 {
|
||||
block: block_size,
|
||||
inline: inline_size,
|
||||
};
|
||||
// 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 independent_layout = replaced.layout(style, &containing_block_for_children);
|
||||
let fragments = replaced.make_fragments(style, size.clone());
|
||||
let relative_adjustement = relative_adjustement(
|
||||
style,
|
||||
inline_size,
|
||||
|
@ -677,14 +671,11 @@ fn layout_in_flow_replaced_block_level<'a>(
|
|||
block: pb.block_start + relative_adjustement.block,
|
||||
inline: pb.inline_start + relative_adjustement.inline + margin.inline_start,
|
||||
},
|
||||
size: Vec2 {
|
||||
block: block_size,
|
||||
inline: inline_size,
|
||||
},
|
||||
size,
|
||||
};
|
||||
BoxFragment {
|
||||
style: style.clone(),
|
||||
children: independent_layout.fragments,
|
||||
children: fragments,
|
||||
content_rect,
|
||||
padding,
|
||||
border,
|
||||
|
|
|
@ -14,12 +14,12 @@ use crate::geom;
|
|||
use crate::geom::flow_relative::Vec2;
|
||||
use crate::positioned::AbsolutelyPositionedBox;
|
||||
use crate::replaced::ReplacedContent;
|
||||
use crate::sizing::ContentSizesRequest;
|
||||
use crate::style_ext::{Direction, Display, DisplayGeneratingBox, DisplayInside, WritingMode};
|
||||
use crate::{ContainingBlock, DefiniteContainingBlock};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelExtend, ParallelIterator};
|
||||
use script_layout_interface::wrapper_traits::LayoutNode;
|
||||
use servo_arc::Arc;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::values::computed::{Length, LengthOrAuto};
|
||||
use style::Zero;
|
||||
use style_traits::CSSPixel;
|
||||
|
@ -28,7 +28,7 @@ pub struct BoxTreeRoot(BlockFormattingContext);
|
|||
pub struct FragmentTreeRoot(Vec<Fragment>);
|
||||
|
||||
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
|
||||
Node: 'dom + Copy + LayoutNode + Send + Sync,
|
||||
{
|
||||
|
@ -41,7 +41,7 @@ impl BoxTreeRoot {
|
|||
}
|
||||
|
||||
fn construct_for_root_element<'dom>(
|
||||
context: &SharedStyleContext<'_>,
|
||||
context: &LayoutContext,
|
||||
root_element: impl NodeExt<'dom>,
|
||||
) -> (ContainsFloats, Vec<Arc<BlockLevelBox>>) {
|
||||
let style = root_element.style(context);
|
||||
|
@ -60,32 +60,33 @@ fn construct_for_root_element<'dom>(
|
|||
Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside,
|
||||
};
|
||||
|
||||
let position = box_style.position;
|
||||
let float = box_style.float;
|
||||
let contents = IndependentFormattingContext::construct(
|
||||
context,
|
||||
style,
|
||||
display_inside,
|
||||
replaced.map_or(Contents::OfElement(root_element), Contents::Replaced),
|
||||
);
|
||||
if position.is_absolutely_positioned() {
|
||||
let contents = replaced.map_or(Contents::OfElement(root_element), Contents::Replaced);
|
||||
if box_style.position.is_absolutely_positioned() {
|
||||
(
|
||||
ContainsFloats::No,
|
||||
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,
|
||||
vec![Arc::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox {
|
||||
contents,
|
||||
}))],
|
||||
vec![Arc::new(BlockLevelBox::OutOfFlowFloatBox(
|
||||
FloatBox::construct(context, style, display_inside, contents),
|
||||
))],
|
||||
)
|
||||
} else {
|
||||
(
|
||||
ContainsFloats::No,
|
||||
vec![Arc::new(BlockLevelBox::Independent(contents))],
|
||||
vec![Arc::new(BlockLevelBox::Independent(
|
||||
IndependentFormattingContext::construct(
|
||||
context,
|
||||
style,
|
||||
display_inside,
|
||||
contents,
|
||||
ContentSizesRequest::None,
|
||||
),
|
||||
))],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ use crate::flow::BlockFormattingContext;
|
|||
use crate::fragments::Fragment;
|
||||
use crate::positioned::AbsolutelyPositionedFragment;
|
||||
use crate::replaced::ReplacedContent;
|
||||
use crate::sizing::{BoxContentSizes, ContentSizesRequest};
|
||||
use crate::style_ext::DisplayInside;
|
||||
use crate::ContainingBlock;
|
||||
use servo_arc::Arc;
|
||||
use std::convert::TryInto;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::Length;
|
||||
|
||||
|
@ -20,6 +20,10 @@ use style::values::computed::Length;
|
|||
#[derive(Debug)]
|
||||
pub(crate) struct IndependentFormattingContext {
|
||||
pub style: Arc<ComputedValues>,
|
||||
|
||||
/// If it was requested during construction
|
||||
pub content_sizes: BoxContentSizes,
|
||||
|
||||
contents: IndependentFormattingContextContents,
|
||||
}
|
||||
|
||||
|
@ -46,22 +50,39 @@ enum NonReplacedIFCKind<'a> {
|
|||
}
|
||||
|
||||
impl IndependentFormattingContext {
|
||||
pub fn construct<'dom, 'style>(
|
||||
context: &SharedStyleContext<'style>,
|
||||
pub fn construct<'dom>(
|
||||
context: &LayoutContext,
|
||||
style: Arc<ComputedValues>,
|
||||
display_inside: DisplayInside,
|
||||
contents: Contents<impl NodeExt<'dom>>,
|
||||
content_sizes: ContentSizesRequest,
|
||||
) -> Self {
|
||||
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 {
|
||||
DisplayInside::Flow | DisplayInside::FlowRoot => Contents::Flow(
|
||||
BlockFormattingContext::construct(context, &style, non_replaced),
|
||||
),
|
||||
DisplayInside::Flow | DisplayInside::FlowRoot => {
|
||||
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> {
|
||||
|
@ -73,24 +94,6 @@ impl IndependentFormattingContext {
|
|||
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> {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#![deny(unsafe_code)]
|
||||
#![feature(exact_size_is_empty)]
|
||||
#![feature(matches_macro)]
|
||||
|
||||
pub mod context;
|
||||
pub mod data;
|
||||
|
@ -18,6 +19,7 @@ mod opaque_node;
|
|||
mod positioned;
|
||||
pub mod query;
|
||||
mod replaced;
|
||||
mod sizing;
|
||||
mod style_ext;
|
||||
pub mod traversal;
|
||||
pub mod wrapper;
|
||||
|
|
|
@ -3,12 +3,16 @@
|
|||
* 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::fragments::{AnonymousFragment, BoxFragment, CollapsedBlockMargins, Fragment};
|
||||
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 rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use servo_arc::Arc;
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
|
||||
use style::Zero;
|
||||
|
||||
|
@ -42,6 +46,32 @@ pub(crate) enum AbsoluteBoxOffsets<NonStatic> {
|
|||
}
|
||||
|
||||
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>(
|
||||
&'a self,
|
||||
initial_start_corner: Vec2<Length>,
|
||||
|
@ -256,10 +286,41 @@ impl<'a> AbsolutelyPositionedFragment<'a> {
|
|||
Anchor::End(end) => cbis - end - pb.inline_sum() - margin.inline_sum(),
|
||||
};
|
||||
|
||||
// FIXME(nox): shrink-to-fit.
|
||||
if self
|
||||
.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 mut absolutely_positioned_fragments = Vec::new();
|
||||
let mut independent_layout = match self.absolutely_positioned_box.contents.as_replaced() {
|
||||
Ok(replaced) => {
|
||||
// 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,
|
||||
|
@ -271,13 +332,14 @@ impl<'a> AbsolutelyPositionedFragment<'a> {
|
|||
"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(
|
||||
non_replaced.layout(
|
||||
layout_context,
|
||||
&containing_block_for_children,
|
||||
dummy_tree_rank,
|
||||
&mut absolutely_positioned_fragments,
|
||||
);
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
let inline_start = match inline_anchor {
|
||||
Anchor::Start(start) => start + pb.inline_start + margin.inline_start,
|
||||
|
@ -307,7 +369,7 @@ impl<'a> AbsolutelyPositionedFragment<'a> {
|
|||
&mut independent_layout.fragments,
|
||||
&content_rect.size,
|
||||
&padding,
|
||||
containing_block_for_children.mode,
|
||||
style.writing_mode(),
|
||||
);
|
||||
|
||||
Fragment::Box(BoxFragment {
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::dom_traversal::NodeExt;
|
||||
use crate::formatting_contexts::IndependentLayout;
|
||||
use crate::fragments::{Fragment, ImageFragment};
|
||||
use crate::geom::{flow_relative, physical};
|
||||
use crate::ContainingBlock;
|
||||
use net_traits::image::base::Image;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use std::sync::Arc;
|
||||
|
@ -35,16 +33,13 @@ impl ReplacedContent {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn layout<'a>(
|
||||
pub fn make_fragments<'a>(
|
||||
&'a self,
|
||||
style: &ServoArc<ComputedValues>,
|
||||
containing_block: &ContainingBlock,
|
||||
) -> IndependentLayout {
|
||||
let (fragments, content_block_size) = match self.kind {
|
||||
ReplacedContentKind::Image(ref image) => {
|
||||
// FIXME(nox): We should not assume block size is known.
|
||||
let block_size = containing_block.block_size.non_auto().unwrap();
|
||||
let fragments = image
|
||||
size: flow_relative::Vec2<Length>,
|
||||
) -> Vec<Fragment> {
|
||||
match &self.kind {
|
||||
ReplacedContentKind::Image(image) => image
|
||||
.as_ref()
|
||||
.and_then(|image| image.id)
|
||||
.map(|image_key| {
|
||||
|
@ -52,22 +47,13 @@ impl ReplacedContent {
|
|||
style: style.clone(),
|
||||
content_rect: flow_relative::Rect {
|
||||
start_corner: flow_relative::Vec2::zero(),
|
||||
size: flow_relative::Vec2 {
|
||||
inline: containing_block.inline_size,
|
||||
block: block_size,
|
||||
},
|
||||
size,
|
||||
},
|
||||
image_key,
|
||||
})
|
||||
})
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
(fragments, block_size)
|
||||
},
|
||||
};
|
||||
IndependentLayout {
|
||||
fragments,
|
||||
content_block_size,
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
152
components/layout_2020/sizing.rs
Normal file
152
components/layout_2020/sizing.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -45,6 +45,9 @@ pub(crate) enum DisplayInside {
|
|||
|
||||
pub(crate) trait ComputedValuesExt {
|
||||
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_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)
|
||||
}
|
||||
|
||||
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]
|
||||
fn box_offsets(&self) -> flow_relative::Sides<LengthPercentageOrAuto> {
|
||||
let position = self.get_position();
|
||||
|
|
|
@ -20,6 +20,10 @@ impl<'a> RecalcStyle<'a> {
|
|||
RecalcStyle { context: context }
|
||||
}
|
||||
|
||||
pub fn context(&self) -> &LayoutContext<'a> {
|
||||
&self.context
|
||||
}
|
||||
|
||||
pub fn destroy(self) -> LayoutContext<'a> {
|
||||
self.context
|
||||
}
|
||||
|
|
|
@ -1081,9 +1081,9 @@ impl LayoutThread {
|
|||
let box_tree = if token.should_traverse() {
|
||||
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 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)
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -170,6 +170,12 @@ impl LengthPercentage {
|
|||
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.
|
||||
#[inline]
|
||||
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
|
||||
/// non-calc percentage.
|
||||
pub fn as_percentage(&self) -> Option<Percentage> {
|
||||
|
|
|
@ -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 {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
|
|
|
@ -92,7 +92,7 @@ where
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
LengthPercentageOrAuto::LengthPercentage(l) => {
|
||||
LengthPercentageOrAuto::LengthPercentage(f(l.clone()))
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[containing-block-008.xht]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[containing-block-010.xht]
|
||||
expected: FAIL
|
Loading…
Add table
Add a link
Reference in a new issue