servo/components/layout_2020/flexbox/construct.rs
Martin Robinson 6d73832009
layout: Do whitespace collapse during breaking and shaping (#31322)
This moves white space collapse to right before breaking and shaping
happens, which is more similar to what happens in legacy layout. This is
the first step toward making this procedure more efficient (avoiding
string copies) and also implementing support for `text-transform`.

Co-authored-by: Rakhi Sharma <atbrakhi@igalia.com>
2024-02-13 23:08:00 +00:00

222 lines
8.1 KiB
Rust

/* 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/. */
use std::borrow::Cow;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use style::values::specified::text::TextDecorationLine;
use super::{FlexContainer, FlexLevelBox};
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom::{BoxSlot, LayoutBox, NodeExt};
use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler};
use crate::flow::BlockFormattingContext;
use crate::formatting_contexts::{
IndependentFormattingContext, NonReplacedFormattingContext,
NonReplacedFormattingContextContents,
};
use crate::positioned::AbsolutelyPositionedBox;
use crate::style_ext::DisplayGeneratingBox;
impl FlexContainer {
pub fn construct<'dom>(
context: &LayoutContext,
info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
contents: NonReplacedContents,
propagated_text_decoration_line: TextDecorationLine,
) -> Self {
let text_decoration_line =
propagated_text_decoration_line | info.style.clone_text_decoration_line();
let mut builder = FlexContainerBuilder {
context,
info,
text_decoration_line,
contiguous_text_runs: Vec::new(),
jobs: Vec::new(),
has_text_runs: false,
};
contents.traverse(context, info, &mut builder);
builder.finish()
}
}
/// <https://drafts.csswg.org/css-flexbox/#flex-items>
struct FlexContainerBuilder<'a, 'dom, Node> {
context: &'a LayoutContext<'a>,
info: &'a NodeAndStyleInfo<Node>,
text_decoration_line: TextDecorationLine,
contiguous_text_runs: Vec<TextRun<'dom, Node>>,
/// To be run in parallel with rayon in `finish`
jobs: Vec<FlexLevelJob<'dom, Node>>,
has_text_runs: bool,
}
enum FlexLevelJob<'dom, Node> {
/// Or pseudo-element
Element {
info: NodeAndStyleInfo<Node>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
},
TextRuns(Vec<TextRun<'dom, Node>>),
}
struct TextRun<'dom, Node> {
info: NodeAndStyleInfo<Node>,
text: Cow<'dom, str>,
}
impl<'a, 'dom, Node: 'dom> TraversalHandler<'dom, Node> for FlexContainerBuilder<'a, 'dom, Node>
where
Node: NodeExt<'dom>,
{
fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) {
self.contiguous_text_runs.push(TextRun {
info: info.clone(),
text,
})
}
/// Or pseudo-element
fn handle_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
) {
// FIXME: are text runs considered "contiguous" if they are only separated
// by an out-of-flow abspos element?
// (That is, are they wrapped in the same anonymous flex item, or each its own?)
self.wrap_any_text_in_anonymous_block_container();
self.jobs.push(FlexLevelJob::Element {
info: info.clone(),
display,
contents,
box_slot,
})
}
}
/// <https://drafts.csswg.org/css-text/#white-space>
fn is_only_document_white_space<Node>(run: &TextRun<'_, Node>) -> bool {
// FIXME: is this the right definition? See
// https://github.com/w3c/csswg-drafts/issues/5146
// https://github.com/w3c/csswg-drafts/issues/5147
run.text
.bytes()
.all(|byte| matches!(byte, b' ' | b'\n' | b'\t'))
}
impl<'a, 'dom, Node: 'dom> FlexContainerBuilder<'a, 'dom, Node>
where
Node: NodeExt<'dom>,
{
fn wrap_any_text_in_anonymous_block_container(&mut self) {
let runs = std::mem::take(&mut self.contiguous_text_runs);
if runs.iter().all(is_only_document_white_space) {
// There is no text run, or they all only contain document white space characters
} else {
self.jobs.push(FlexLevelJob::TextRuns(runs));
self.has_text_runs = true;
}
}
fn finish(mut self) -> FlexContainer {
self.wrap_any_text_in_anonymous_block_container();
let anonymous_style = if self.has_text_runs {
Some(
self.context
.shared_context()
.stylist
.style_for_anonymous::<Node::ConcreteElement>(
&self.context.shared_context().guards,
&style::selector_parser::PseudoElement::ServoAnonymousBox,
&self.info.style,
),
)
} else {
None
};
let mut children = std::mem::take(&mut self.jobs)
.into_par_iter()
.map(|job| match job {
FlexLevelJob::TextRuns(runs) => ArcRefCell::new(FlexLevelBox::FlexItem({
let runs = runs.into_iter().map(|run| {
crate::flow::text_run::TextRun::new(
(&run.info).into(),
run.info.style,
run.text.into(),
)
});
let bfc = BlockFormattingContext::construct_for_text_runs(
runs,
self.context,
self.text_decoration_line,
);
let info = &self
.info
.new_replacing_style(anonymous_style.clone().unwrap());
IndependentFormattingContext::NonReplaced(NonReplacedFormattingContext {
base_fragment_info: info.into(),
style: info.style.clone(),
content_sizes: None,
contents: NonReplacedFormattingContextContents::Flow(bfc),
})
})),
FlexLevelJob::Element {
info,
display,
contents,
box_slot,
} => {
let display_inside = match display {
DisplayGeneratingBox::OutsideInside { inside, .. } => inside,
DisplayGeneratingBox::LayoutInternal(_) => display.display_inside(),
};
let box_ = if info.style.get_box().position.is_absolutely_positioned() {
// https://drafts.csswg.org/css-flexbox/#abspos-items
ArcRefCell::new(FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(
ArcRefCell::new(AbsolutelyPositionedBox::construct(
self.context,
&info,
display_inside,
contents,
)),
))
} else {
ArcRefCell::new(FlexLevelBox::FlexItem(
IndependentFormattingContext::construct(
self.context,
&info,
display_inside,
contents,
self.text_decoration_line,
),
))
};
box_slot.set(LayoutBox::FlexLevel(box_.clone()));
box_
},
})
.collect::<Vec<_>>();
// https://drafts.csswg.org/css-flexbox/#order-modified-document-order
children.sort_by_key(|child| match &*child.borrow() {
FlexLevelBox::FlexItem(item) => item.style().clone_order(),
// “Absolutely-positioned children of a flex container are treated
// as having order: 0 for the purpose of determining their painting order
// relative to flex items.”
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => 0,
});
FlexContainer { children }
}
}