diff options
author | Simon Sapin <simon.sapin@exyr.org> | 2019-09-09 19:23:36 +0200 |
---|---|---|
committer | Anthony Ramine <nox@nox.paris> | 2019-09-11 10:06:35 +0200 |
commit | 86904757e66285412de162483f3ba2a6db35403e (patch) | |
tree | 8fa6e2682aaa3e63ff684a38750b44f1bec9bea5 /components/layout_2020/flow/construct.rs | |
parent | be0e84b30f5b3ffa708b81348e66748ec43dfc8a (diff) | |
download | servo-86904757e66285412de162483f3ba2a6db35403e.tar.gz servo-86904757e66285412de162483f3ba2a6db35403e.zip |
Import files from Victor
https://github.com/SimonSapin/victor/tree/fdb11f3e87f6d2d59170d10169fa6deb94e53b94
Diffstat (limited to 'components/layout_2020/flow/construct.rs')
-rw-r--r-- | components/layout_2020/flow/construct.rs | 625 |
1 files changed, 625 insertions, 0 deletions
diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs new file mode 100644 index 00000000000..4acfc93ce92 --- /dev/null +++ b/components/layout_2020/flow/construct.rs @@ -0,0 +1,625 @@ +use super::*; + +impl BlockFormattingContext { + pub fn construct<'a>( + context: &'a Context<'a>, + style: &'a Arc<ComputedValues>, + contents: NonReplacedContents, + ) -> Self { + let (contents, contains_floats) = BlockContainer::construct(context, style, contents); + Self { + contents, + contains_floats: contains_floats == ContainsFloats::Yes, + } + } +} + +enum IntermediateBlockLevelBox { + SameFormattingContextBlock { + style: Arc<ComputedValues>, + contents: IntermediateBlockContainer, + }, + Independent { + style: Arc<ComputedValues>, + display_inside: DisplayInside, + contents: Contents, + }, + OutOfFlowAbsolutelyPositionedBox { + style: Arc<ComputedValues>, + display_inside: DisplayInside, + contents: Contents, + }, + OutOfFlowFloatBox { + style: Arc<ComputedValues>, + display_inside: DisplayInside, + contents: Contents, + }, +} + +/// A block container that may still have to be constructed. +/// +/// Represents either the inline formatting context of an anonymous block +/// box or the yet-to-be-computed block container generated from the children +/// of a given element. +/// +/// Deferring allows using rayon’s `into_par_iter`. +enum IntermediateBlockContainer { + InlineFormattingContext(InlineFormattingContext), + Deferred { contents: NonReplacedContents }, +} + +/// A builder for a block container. +/// +/// 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<'a> { + context: &'a Context<'a>, + block_container_style: &'a Arc<ComputedValues>, + + /// The list of block-level boxes of the final block container. + /// + /// Contains all the complete block level boxes we found traversing the tree + /// so far, if this is empty at the end of the traversal and the ongoing + /// inline formatting context is not empty, the block container establishes + /// an inline formatting context (see end of `build`). + /// + /// DOM nodes which represent block-level boxes are immediately pushed + /// to this list with their style without ever being traversed at this + /// point, instead we just move to their next sibling. If the DOM node + /// doesn't have a next sibling, we either reached the end of the container + /// root or there are ongoing inline-level boxes + /// (see `handle_block_level_element`). + block_level_boxes: Vec<(IntermediateBlockLevelBox, BoxSlot<'a>)>, + + /// The ongoing inline formatting context of the builder. + /// + /// Contains all the complete inline level boxes we found traversing the + /// tree so far. If a block-level box is found during traversal, + /// this inline formatting context is pushed as a block level box to + /// the list of block-level boxes of the builder + /// (see `end_ongoing_inline_formatting_context`). + ongoing_inline_formatting_context: InlineFormattingContext, + + /// The ongoing stack of inline boxes stack of the builder. + /// + /// Contains all the currently ongoing inline boxes we entered so far. + /// The traversal is at all times as deep in the tree as this stack is, + /// which is why the code doesn't need to keep track of the actual + /// container root (see `handle_inline_level_element`). + /// + /// Whenever the end of a DOM element that represents an inline box is + /// reached, the inline box at the top of this stack is complete and ready + /// to be pushed to the children of the next last ongoing inline box + /// the ongoing inline formatting context if the stack is now empty, + /// which means we reached the end of a child of the actual + /// container root (see `move_to_next_sibling`). + ongoing_inline_boxes_stack: Vec<InlineBox>, + + /// The style of the anonymous block boxes pushed to the list of block-level + /// boxes, if any (see `end_ongoing_inline_formatting_context`). + anonymous_style: Option<Arc<ComputedValues>>, + + /// Whether the resulting block container contains any float box. + contains_floats: ContainsFloats, +} + +impl BlockContainer { + pub fn construct<'a>( + context: &'a Context<'a>, + block_container_style: &'a Arc<ComputedValues>, + contents: NonReplacedContents, + ) -> (BlockContainer, ContainsFloats) { + 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(), + }; + + contents.traverse(block_container_style, context, &mut builder); + + debug_assert!(builder.ongoing_inline_boxes_stack.is_empty()); + + if !builder + .ongoing_inline_formatting_context + .inline_level_boxes + .is_empty() + { + if builder.block_level_boxes.is_empty() { + let container = BlockContainer::InlineFormattingContext( + builder.ongoing_inline_formatting_context, + ); + return (container, builder.contains_floats); + } + 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)| { + let (block_level_box, box_contains_floats) = intermediate.finish(context); + *contains_floats |= box_contains_floats; + box_slot.set(LayoutBox::BlockLevel(block_level_box.clone())); + block_level_box + }, + |left, right| *left |= right, + ) + .collect(), + ); + (container, contains_floats) + } +} + +impl<'a> TraversalHandler<'a> for BlockContainerBuilder<'a> { + fn handle_element( + &mut self, + style: &Arc<ComputedValues>, + display: DisplayGeneratingBox, + contents: Contents, + box_slot: BoxSlot<'a>, + ) { + match display { + DisplayGeneratingBox::OutsideInside { outside, inside } => match outside { + DisplayOutside::Inline => box_slot.set(LayoutBox::InlineLevel( + self.handle_inline_level_element(style, inside, contents), + )), + DisplayOutside::Block => { + // Floats and abspos cause blockification, so they only happen in this case. + // https://drafts.csswg.org/css2/visuren.html#dis-pos-flo + if style.box_.position.is_absolutely_positioned() { + self.handle_absolutely_positioned_element( + style.clone(), + inside, + contents, + box_slot, + ) + } else if style.box_.float.is_floating() { + self.handle_float_element(style.clone(), inside, contents, box_slot) + } else { + self.handle_block_level_element(style.clone(), inside, contents, box_slot) + } + } + }, + } + } + + fn handle_text(&mut self, input: &str, parent_style: &Arc<ComputedValues>) { + let (leading_whitespace, mut input) = self.handle_leading_whitespace(input); + if leading_whitespace || !input.is_empty() { + // This text node should be pushed either to the next ongoing + // inline level box with the parent style of that inline level box + // that will be ended, or directly to the ongoing inline formatting + // context with the parent style of that builder. + let inlines = self.current_inline_level_boxes(); + + fn last_text(inlines: &mut [Arc<InlineLevelBox>]) -> Option<&mut String> { + let last = inlines.last_mut()?; + if let InlineLevelBox::TextRun(_) = &**last { + // We never clone text run boxes, so the refcount is 1 and unwrap succeeds: + let last = Arc::get_mut(last).unwrap(); + if let InlineLevelBox::TextRun(TextRun { text, .. }) = last { + Some(text) + } else { + unreachable!() + } + } else { + None + } + } + + let mut new_text_run_contents; + let output; + if let Some(text) = last_text(inlines) { + // Append to the existing text run + new_text_run_contents = None; + output = text; + } else { + new_text_run_contents = Some(String::new()); + output = new_text_run_contents.as_mut().unwrap(); + } + + if leading_whitespace { + output.push(' ') + } + loop { + if let Some(i) = input.bytes().position(|b| b.is_ascii_whitespace()) { + let (non_whitespace, rest) = input.split_at(i); + output.push_str(non_whitespace); + output.push(' '); + if let Some(i) = rest.bytes().position(|b| !b.is_ascii_whitespace()) { + input = &rest[i..]; + } else { + break; + } + } else { + output.push_str(input); + break; + } + } + + if let Some(text) = new_text_run_contents { + let parent_style = parent_style.clone(); + inlines.push(Arc::new(InlineLevelBox::TextRun(TextRun { + parent_style, + text, + }))) + } + } + } +} + +impl<'a> BlockContainerBuilder<'a> { + /// Returns: + /// + /// * Whether this text run has preserved (non-collapsible) leading whitespace + /// * The contents starting at the first non-whitespace character (or the empty string) + fn handle_leading_whitespace<'text>(&mut self, text: &'text str) -> (bool, &'text str) { + // FIXME: this is only an approximation of + // https://drafts.csswg.org/css2/text.html#white-space-model + if !text.starts_with(|c: char| c.is_ascii_whitespace()) { + return (false, text); + } + let mut inline_level_boxes = self.current_inline_level_boxes().iter().rev(); + let mut stack = Vec::new(); + let preserved = loop { + match inline_level_boxes.next().map(|b| &**b) { + Some(InlineLevelBox::TextRun(r)) => break !r.text.ends_with(' '), + Some(InlineLevelBox::Atomic { .. }) => break false, + Some(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(_)) + | Some(InlineLevelBox::OutOfFlowFloatBox(_)) => {} + Some(InlineLevelBox::InlineBox(b)) => { + stack.push(inline_level_boxes); + inline_level_boxes = b.children.iter().rev() + } + None => { + if let Some(iter) = stack.pop() { + inline_level_boxes = iter + } else { + break false; // Paragraph start + } + } + } + }; + let text = text.trim_start_matches(|c: char| c.is_ascii_whitespace()); + (preserved, text) + } + + fn handle_inline_level_element( + &mut self, + style: &Arc<ComputedValues>, + display_inside: DisplayInside, + contents: Contents, + ) -> Arc<InlineLevelBox> { + let box_ = match contents.try_into() { + Err(replaced) => Arc::new(InlineLevelBox::Atomic { + style: style.clone(), + contents: replaced, + }), + Ok(non_replaced) => match display_inside { + DisplayInside::Flow => { + // 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)) + } + DisplayInside::FlowRoot => { + // a.k.a. `inline-block` + unimplemented!() + } + }, + }; + self.current_inline_level_boxes().push(box_.clone()); + box_ + } + + fn handle_block_level_element( + &mut self, + style: Arc<ComputedValues>, + display_inside: DisplayInside, + contents: Contents, + box_slot: BoxSlot<'a>, + ) { + // We just found a block level element, all ongoing inline level boxes + // need to be split around it. We iterate on the fragmented inline + // level box stack to take their contents and set their first_fragment + // field to false, for the fragmented inline level boxes that will + // come after the block level element. + let mut fragmented_inline_boxes = + self.ongoing_inline_boxes_stack + .iter_mut() + .rev() + .map(|ongoing| { + let fragmented = InlineBox { + style: ongoing.style.clone(), + first_fragment: ongoing.first_fragment, + // The fragmented boxes before the block level element + // are obviously not the last fragment. + last_fragment: false, + children: take(&mut ongoing.children), + }; + ongoing.first_fragment = false; + fragmented + }); + + if let Some(last) = fragmented_inline_boxes.next() { + // There were indeed some ongoing inline level boxes before + // the block, we accumulate them as a single inline level box + // to be pushed to the ongoing inline formatting context. + let mut fragmented_inline = InlineLevelBox::InlineBox(last); + for mut fragmented_parent_inline_box in fragmented_inline_boxes { + fragmented_parent_inline_box + .children + .push(Arc::new(fragmented_inline)); + fragmented_inline = InlineLevelBox::InlineBox(fragmented_parent_inline_box); + } + + self.ongoing_inline_formatting_context + .inline_level_boxes + .push(Arc::new(fragmented_inline)); + } + + // We found a block level element, so the ongoing inline formatting + // context needs to be ended. + self.end_ongoing_inline_formatting_context(); + + let intermediate_box = match contents.try_into() { + Ok(contents) => match display_inside { + DisplayInside::Flow => IntermediateBlockLevelBox::SameFormattingContextBlock { + style, + contents: IntermediateBlockContainer::Deferred { contents }, + }, + _ => IntermediateBlockLevelBox::Independent { + style, + display_inside, + contents: contents.into(), + }, + }, + Err(contents) => { + let contents = Contents::Replaced(contents); + IntermediateBlockLevelBox::Independent { + style, + display_inside, + contents, + } + } + }; + self.block_level_boxes.push((intermediate_box, box_slot)) + } + + fn handle_absolutely_positioned_element( + &mut self, + style: Arc<ComputedValues>, + display_inside: DisplayInside, + contents: Contents, + box_slot: BoxSlot<'a>, + ) { + if !self.has_ongoing_inline_formatting_context() { + let box_ = IntermediateBlockLevelBox::OutOfFlowAbsolutelyPositionedBox { + style, + contents, + display_inside, + }; + 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, + ), + style, + }, + )); + self.current_inline_level_boxes().push(box_.clone()); + box_slot.set(LayoutBox::InlineLevel(box_)) + } + } + + fn handle_float_element( + &mut self, + style: Arc<ComputedValues>, + display_inside: DisplayInside, + contents: Contents, + box_slot: BoxSlot<'a>, + ) { + self.contains_floats = ContainsFloats::Yes; + + if !self.has_ongoing_inline_formatting_context() { + let box_ = IntermediateBlockLevelBox::OutOfFlowFloatBox { + style, + contents, + display_inside, + }; + self.block_level_boxes.push((box_, box_slot)); + } else { + let box_ = Arc::new(InlineLevelBox::OutOfFlowFloatBox(FloatBox { + contents: IndependentFormattingContext::construct( + self.context, + &style, + display_inside, + contents, + ), + style, + })); + self.current_inline_level_boxes().push(box_.clone()); + box_slot.set(LayoutBox::InlineLevel(box_)) + } + } + + fn end_ongoing_inline_formatting_context(&mut self) { + assert!( + self.ongoing_inline_boxes_stack.is_empty(), + "there should be no ongoing inline level boxes", + ); + + if self + .ongoing_inline_formatting_context + .inline_level_boxes + .is_empty() + { + // There should never be an empty inline formatting context. + return; + } + + let block_container_style = self.block_container_style; + let anonymous_style = self.anonymous_style.get_or_insert_with(|| { + // If parent_style is None, the parent is the document node, + // in which case anonymous inline boxes should inherit their + // styles from initial values. + ComputedValues::anonymous_inheriting_from(Some(block_container_style)) + }); + + let box_ = IntermediateBlockLevelBox::SameFormattingContextBlock { + style: anonymous_style.clone(), + contents: IntermediateBlockContainer::InlineFormattingContext(take( + &mut self.ongoing_inline_formatting_context, + )), + }; + self.block_level_boxes.push((box_, BoxSlot::dummy())) + } + + fn current_inline_level_boxes(&mut self) -> &mut Vec<Arc<InlineLevelBox>> { + match self.ongoing_inline_boxes_stack.last_mut() { + Some(last) => &mut last.children, + None => &mut self.ongoing_inline_formatting_context.inline_level_boxes, + } + } + + fn has_ongoing_inline_formatting_context(&self) -> bool { + !self + .ongoing_inline_formatting_context + .inline_level_boxes + .is_empty() + || !self.ongoing_inline_boxes_stack.is_empty() + } +} + +impl IntermediateBlockLevelBox { + fn finish(self, context: &Context) -> (Arc<BlockLevelBox>, ContainsFloats) { + match self { + IntermediateBlockLevelBox::SameFormattingContextBlock { style, contents } => { + let (contents, contains_floats) = contents.finish(context, &style); + let block_level_box = + Arc::new(BlockLevelBox::SameFormattingContextBlock { contents, style }); + (block_level_box, contains_floats) + } + IntermediateBlockLevelBox::Independent { + style, + display_inside, + contents, + } => { + let contents = IndependentFormattingContext::construct( + context, + &style, + display_inside, + contents, + ); + ( + Arc::new(BlockLevelBox::Independent { style, contents }), + ContainsFloats::No, + ) + } + IntermediateBlockLevelBox::OutOfFlowAbsolutelyPositionedBox { + style, + display_inside, + contents, + } => { + let block_level_box = Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox( + AbsolutelyPositionedBox { + contents: IndependentFormattingContext::construct( + context, + &style, + display_inside, + contents, + ), + style: style, + }, + )); + (block_level_box, ContainsFloats::No) + } + IntermediateBlockLevelBox::OutOfFlowFloatBox { + style, + display_inside, + contents, + } => { + let contents = IndependentFormattingContext::construct( + context, + &style, + display_inside, + contents, + ); + let block_level_box = Arc::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox { + contents, + style, + })); + (block_level_box, ContainsFloats::Yes) + } + } + } +} + +impl IntermediateBlockContainer { + fn finish( + self, + context: &Context, + style: &Arc<ComputedValues>, + ) -> (BlockContainer, ContainsFloats) { + match self { + IntermediateBlockContainer::Deferred { contents } => { + BlockContainer::construct(context, style, contents) + } + IntermediateBlockContainer::InlineFormattingContext(ifc) => { + // 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, + ) + } + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(in crate::layout) enum ContainsFloats { + No, + Yes, +} + +impl std::ops::BitOrAssign for ContainsFloats { + fn bitor_assign(&mut self, other: Self) { + if other == ContainsFloats::Yes { + *self = ContainsFloats::Yes; + } + } +} + +impl Default for ContainsFloats { + fn default() -> Self { + ContainsFloats::No + } +} |