mirror of
https://github.com/servo/servo.git
synced 2025-08-04 21:20:23 +01:00
parent
be0e84b30f
commit
86904757e6
12 changed files with 2728 additions and 0 deletions
344
components/layout_2020/dom_traversal.rs
Normal file
344
components/layout_2020/dom_traversal.rs
Normal file
|
@ -0,0 +1,344 @@
|
|||
use super::*;
|
||||
use crate::dom::{Document, NodeData, NodeId};
|
||||
use crate::style::StyleSet;
|
||||
use atomic_refcell::AtomicRefMut;
|
||||
|
||||
pub(super) struct Context<'a> {
|
||||
pub document: &'a Document,
|
||||
pub author_styles: &'a StyleSet,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(super) enum WhichPseudoElement {
|
||||
Before,
|
||||
After,
|
||||
}
|
||||
|
||||
pub(super) enum Contents {
|
||||
/// Refers to a DOM subtree, plus `::before` and `::after` pseudo-elements.
|
||||
OfElement(NodeId),
|
||||
|
||||
/// Example: an `<img src=…>` element.
|
||||
/// <https://drafts.csswg.org/css2/conform.html#replaced-element>
|
||||
Replaced(ReplacedContent),
|
||||
|
||||
/// Content of a `::before` or `::after` pseudo-element this is being generated.
|
||||
/// <https://drafts.csswg.org/css2/generate.html#content>
|
||||
OfPseudoElement(Vec<PseudoElementContentItem>),
|
||||
}
|
||||
|
||||
pub(super) enum NonReplacedContents {
|
||||
OfElement(NodeId),
|
||||
OfPseudoElement(Vec<PseudoElementContentItem>),
|
||||
}
|
||||
|
||||
pub(super) enum PseudoElementContentItem {
|
||||
Text(String),
|
||||
Replaced(ReplacedContent),
|
||||
}
|
||||
|
||||
pub(super) trait TraversalHandler<'dom> {
|
||||
fn handle_text(&mut self, text: &str, parent_style: &Arc<ComputedValues>);
|
||||
|
||||
/// Or pseudo-element
|
||||
fn handle_element(
|
||||
&mut self,
|
||||
style: &Arc<ComputedValues>,
|
||||
display: DisplayGeneratingBox,
|
||||
contents: Contents,
|
||||
box_slot: BoxSlot<'dom>,
|
||||
);
|
||||
}
|
||||
|
||||
fn traverse_children_of<'dom>(
|
||||
parent_element: NodeId,
|
||||
parent_element_style: &Arc<ComputedValues>,
|
||||
context: &'dom Context,
|
||||
handler: &mut impl TraversalHandler<'dom>,
|
||||
) {
|
||||
traverse_pseudo_element(
|
||||
WhichPseudoElement::Before,
|
||||
parent_element,
|
||||
parent_element_style,
|
||||
context,
|
||||
handler,
|
||||
);
|
||||
|
||||
let mut next = context.document[parent_element].first_child;
|
||||
while let Some(child) = next {
|
||||
match &context.document[child].data {
|
||||
NodeData::Document
|
||||
| NodeData::Doctype { .. }
|
||||
| NodeData::Comment { .. }
|
||||
| NodeData::ProcessingInstruction { .. } => {}
|
||||
NodeData::Text { contents } => {
|
||||
handler.handle_text(contents, parent_element_style);
|
||||
}
|
||||
NodeData::Element(_) => traverse_element(child, parent_element_style, context, handler),
|
||||
}
|
||||
next = context.document[child].next_sibling
|
||||
}
|
||||
|
||||
traverse_pseudo_element(
|
||||
WhichPseudoElement::After,
|
||||
parent_element,
|
||||
&parent_element_style,
|
||||
context,
|
||||
handler,
|
||||
);
|
||||
}
|
||||
|
||||
fn traverse_element<'dom>(
|
||||
element_id: NodeId,
|
||||
parent_element_style: &ComputedValues,
|
||||
context: &'dom Context,
|
||||
handler: &mut impl TraversalHandler<'dom>,
|
||||
) {
|
||||
let style = style_for_element(
|
||||
context.author_styles,
|
||||
context.document,
|
||||
element_id,
|
||||
Some(parent_element_style),
|
||||
);
|
||||
match style.box_.display {
|
||||
Display::None => context.unset_boxes_in_subtree(element_id),
|
||||
Display::Contents => {
|
||||
if ReplacedContent::for_element(element_id, context).is_some() {
|
||||
// `display: content` on a replaced element computes to `display: none`
|
||||
// <https://drafts.csswg.org/css-display-3/#valdef-display-contents>
|
||||
context.unset_boxes_in_subtree(element_id)
|
||||
} else {
|
||||
context.layout_data_mut(element_id).self_box = Some(LayoutBox::DisplayContents);
|
||||
traverse_children_of(element_id, &style, context, handler)
|
||||
}
|
||||
}
|
||||
Display::GeneratingBox(display) => handler.handle_element(
|
||||
&style,
|
||||
display,
|
||||
match ReplacedContent::for_element(element_id, context) {
|
||||
Some(replaced) => Contents::Replaced(replaced),
|
||||
None => Contents::OfElement(element_id),
|
||||
},
|
||||
context.element_box_slot(element_id),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn traverse_pseudo_element<'dom>(
|
||||
which: WhichPseudoElement,
|
||||
element: NodeId,
|
||||
element_style: &ComputedValues,
|
||||
context: &'dom Context,
|
||||
handler: &mut impl TraversalHandler<'dom>,
|
||||
) {
|
||||
if let Some(style) = pseudo_element_style(which, element, element_style, context) {
|
||||
match style.box_.display {
|
||||
Display::None => context.unset_pseudo_element_box(element, which),
|
||||
Display::Contents => {
|
||||
context.unset_pseudo_element_box(element, which);
|
||||
let items = generate_pseudo_element_content(&style, element, context);
|
||||
traverse_pseudo_element_contents(&style, items, handler);
|
||||
}
|
||||
Display::GeneratingBox(display) => {
|
||||
let items = generate_pseudo_element_content(&style, element, context);
|
||||
let contents = Contents::OfPseudoElement(items);
|
||||
let box_slot = context.pseudo_element_box_slot(element, which);
|
||||
handler.handle_element(&style, display, contents, box_slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn traverse_pseudo_element_contents<'dom>(
|
||||
pseudo_element_style: &Arc<ComputedValues>,
|
||||
items: Vec<PseudoElementContentItem>,
|
||||
handler: &mut impl TraversalHandler<'dom>,
|
||||
) {
|
||||
let mut anonymous_style = None;
|
||||
for item in items {
|
||||
match item {
|
||||
PseudoElementContentItem::Text(text) => {
|
||||
handler.handle_text(&text, pseudo_element_style)
|
||||
}
|
||||
PseudoElementContentItem::Replaced(contents) => {
|
||||
let item_style = anonymous_style.get_or_insert_with(|| {
|
||||
ComputedValues::anonymous_inheriting_from(Some(pseudo_element_style))
|
||||
});
|
||||
let display_inline = DisplayGeneratingBox::OutsideInside {
|
||||
outside: DisplayOutside::Inline,
|
||||
inside: DisplayInside::Flow,
|
||||
};
|
||||
// `display` is not inherited, so we get the initial value
|
||||
debug_assert!(item_style.box_.display == Display::GeneratingBox(display_inline));
|
||||
handler.handle_element(
|
||||
item_style,
|
||||
display_inline,
|
||||
Contents::Replaced(contents),
|
||||
// We don’t keep pointers to boxes generated by contents of pseudo-elements
|
||||
BoxSlot::dummy(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Contents> for NonReplacedContents {
|
||||
type Error = ReplacedContent;
|
||||
|
||||
fn try_from(contents: Contents) -> Result<Self, Self::Error> {
|
||||
match contents {
|
||||
Contents::OfElement(id) => Ok(NonReplacedContents::OfElement(id)),
|
||||
Contents::OfPseudoElement(items) => Ok(NonReplacedContents::OfPseudoElement(items)),
|
||||
Contents::Replaced(replaced) => Err(replaced),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<NonReplacedContents> for Contents {
|
||||
fn from(contents: NonReplacedContents) -> Self {
|
||||
match contents {
|
||||
NonReplacedContents::OfElement(id) => Contents::OfElement(id),
|
||||
NonReplacedContents::OfPseudoElement(items) => Contents::OfPseudoElement(items),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NonReplacedContents {
|
||||
pub fn traverse<'dom>(
|
||||
self,
|
||||
inherited_style: &Arc<ComputedValues>,
|
||||
context: &'dom Context,
|
||||
handler: &mut impl TraversalHandler<'dom>,
|
||||
) {
|
||||
match self {
|
||||
NonReplacedContents::OfElement(id) => {
|
||||
traverse_children_of(id, inherited_style, context, handler)
|
||||
}
|
||||
NonReplacedContents::OfPseudoElement(items) => {
|
||||
traverse_pseudo_element_contents(inherited_style, items, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pseudo_element_style(
|
||||
_which: WhichPseudoElement,
|
||||
_element: NodeId,
|
||||
_element_style: &ComputedValues,
|
||||
_context: &Context,
|
||||
) -> Option<Arc<ComputedValues>> {
|
||||
// FIXME: run the cascade, then return None for `content: normal` or `content: none`
|
||||
// https://drafts.csswg.org/css2/generate.html#content
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_pseudo_element_content(
|
||||
_pseudo_element_style: &ComputedValues,
|
||||
_element: NodeId,
|
||||
_context: &Context,
|
||||
) -> Vec<PseudoElementContentItem> {
|
||||
let _ = PseudoElementContentItem::Text;
|
||||
let _ = PseudoElementContentItem::Replaced;
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub(super) struct BoxSlot<'dom> {
|
||||
slot: Option<AtomicRefMut<'dom, Option<LayoutBox>>>,
|
||||
}
|
||||
|
||||
impl<'dom> BoxSlot<'dom> {
|
||||
pub fn new(mut slot: AtomicRefMut<'dom, Option<LayoutBox>>) -> Self {
|
||||
*slot = None;
|
||||
Self { slot: Some(slot) }
|
||||
}
|
||||
|
||||
pub fn dummy() -> Self {
|
||||
Self { slot: None }
|
||||
}
|
||||
|
||||
pub fn set(mut self, box_: LayoutBox) {
|
||||
if let Some(slot) = &mut self.slot {
|
||||
**slot = Some(box_)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BoxSlot<'_> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(slot) = &mut self.slot {
|
||||
assert!(slot.is_some(), "failed to set a layout box")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context<'_> {
|
||||
fn layout_data_mut(&self, element_id: NodeId) -> AtomicRefMut<LayoutDataForElement> {
|
||||
self.document[element_id]
|
||||
.as_element()
|
||||
.unwrap()
|
||||
.layout_data
|
||||
.borrow_mut()
|
||||
}
|
||||
|
||||
fn element_box_slot(&self, element_id: NodeId) -> BoxSlot {
|
||||
BoxSlot::new(AtomicRefMut::map(
|
||||
self.layout_data_mut(element_id),
|
||||
|data| &mut data.self_box,
|
||||
))
|
||||
}
|
||||
|
||||
fn pseudo_element_box_slot(&self, element_id: NodeId, which: WhichPseudoElement) -> BoxSlot {
|
||||
BoxSlot::new(AtomicRefMut::map(
|
||||
self.layout_data_mut(element_id),
|
||||
|data| {
|
||||
let pseudos = data.pseudo_elements.get_or_insert_with(Default::default);
|
||||
match which {
|
||||
WhichPseudoElement::Before => &mut pseudos.before,
|
||||
WhichPseudoElement::After => &mut pseudos.after,
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn unset_pseudo_element_box(&self, element_id: NodeId, which: WhichPseudoElement) {
|
||||
if let Some(pseudos) = &mut self.layout_data_mut(element_id).pseudo_elements {
|
||||
match which {
|
||||
WhichPseudoElement::Before => pseudos.before = None,
|
||||
WhichPseudoElement::After => pseudos.after = None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unset_boxes_in_subtree(&self, base_element: NodeId) {
|
||||
let mut node_id = base_element;
|
||||
loop {
|
||||
let node = &self.document[node_id];
|
||||
if let Some(element_data) = node.as_element() {
|
||||
let mut layout_data = element_data.layout_data.borrow_mut();
|
||||
layout_data.pseudo_elements = None;
|
||||
if layout_data.self_box.take().is_some() {
|
||||
// Only descend into children if we removed a box.
|
||||
// If there wasn’t one, then descendants don’t have boxes either.
|
||||
if let Some(child) = node.first_child {
|
||||
node_id = child;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut next_is_a_sibling_of = node_id;
|
||||
node_id = loop {
|
||||
if let Some(sibling) = self.document[next_is_a_sibling_of].next_sibling {
|
||||
break sibling;
|
||||
} else {
|
||||
next_is_a_sibling_of = node
|
||||
.parent
|
||||
.expect("reached the root while traversing only a subtree");
|
||||
}
|
||||
};
|
||||
if next_is_a_sibling_of == base_element {
|
||||
// Don’t go outside the subtree
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
components/layout_2020/element_data.rs
Normal file
19
components/layout_2020/element_data.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct LayoutDataForElement {
|
||||
pub(super) self_box: Option<LayoutBox>,
|
||||
pub(super) pseudo_elements: Option<Box<PseudoElementBoxes>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct PseudoElementBoxes {
|
||||
pub before: Option<LayoutBox>,
|
||||
pub after: Option<LayoutBox>,
|
||||
}
|
||||
|
||||
pub(super) enum LayoutBox {
|
||||
DisplayContents,
|
||||
BlockLevel(Arc<BlockLevelBox>),
|
||||
InlineLevel(Arc<InlineLevelBox>),
|
||||
}
|
625
components/layout_2020/flow/construct.rs
Normal file
625
components/layout_2020/flow/construct.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
18
components/layout_2020/flow/float.rs
Normal file
18
components/layout_2020/flow/float.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(in crate::layout) struct FloatBox {
|
||||
pub style: Arc<ComputedValues>,
|
||||
pub contents: IndependentFormattingContext,
|
||||
}
|
||||
|
||||
/// Data kept during layout about the floats in a given block formatting context.
|
||||
pub(in crate::layout) struct FloatContext {
|
||||
// TODO
|
||||
}
|
||||
|
||||
impl FloatContext {
|
||||
pub fn new() -> Self {
|
||||
FloatContext {}
|
||||
}
|
||||
}
|
345
components/layout_2020/flow/inline.rs
Normal file
345
components/layout_2020/flow/inline.rs
Normal file
|
@ -0,0 +1,345 @@
|
|||
use super::*;
|
||||
use crate::fonts::BITSTREAM_VERA_SANS;
|
||||
use crate::text::ShapedSegment;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(in crate::layout) struct InlineFormattingContext {
|
||||
pub(super) inline_level_boxes: Vec<Arc<InlineLevelBox>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(in crate::layout) enum InlineLevelBox {
|
||||
InlineBox(InlineBox),
|
||||
TextRun(TextRun),
|
||||
OutOfFlowAbsolutelyPositionedBox(AbsolutelyPositionedBox),
|
||||
OutOfFlowFloatBox(FloatBox),
|
||||
Atomic {
|
||||
style: Arc<ComputedValues>,
|
||||
// FIXME: this should be IndependentFormattingContext:
|
||||
contents: ReplacedContent,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(in crate::layout) struct InlineBox {
|
||||
pub style: Arc<ComputedValues>,
|
||||
pub first_fragment: bool,
|
||||
pub last_fragment: bool,
|
||||
pub children: Vec<Arc<InlineLevelBox>>,
|
||||
}
|
||||
|
||||
/// https://www.w3.org/TR/css-display-3/#css-text-run
|
||||
#[derive(Debug)]
|
||||
pub(in crate::layout) struct TextRun {
|
||||
pub parent_style: Arc<ComputedValues>,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
struct InlineNestingLevelState<'box_tree> {
|
||||
remaining_boxes: std::slice::Iter<'box_tree, Arc<InlineLevelBox>>,
|
||||
fragments_so_far: Vec<Fragment>,
|
||||
inline_start: Length,
|
||||
max_block_size_of_fragments_so_far: Length,
|
||||
}
|
||||
|
||||
struct PartialInlineBoxFragment<'box_tree> {
|
||||
style: Arc<ComputedValues>,
|
||||
start_corner: Vec2<Length>,
|
||||
padding: Sides<Length>,
|
||||
border: Sides<Length>,
|
||||
margin: Sides<Length>,
|
||||
last_box_tree_fragment: bool,
|
||||
parent_nesting_level: InlineNestingLevelState<'box_tree>,
|
||||
}
|
||||
|
||||
struct InlineFormattingContextState<'box_tree, 'cb> {
|
||||
containing_block: &'cb ContainingBlock,
|
||||
line_boxes: LinesBoxes,
|
||||
inline_position: Length,
|
||||
partial_inline_boxes_stack: Vec<PartialInlineBoxFragment<'box_tree>>,
|
||||
current_nesting_level: InlineNestingLevelState<'box_tree>,
|
||||
}
|
||||
|
||||
struct LinesBoxes {
|
||||
boxes: Vec<Fragment>,
|
||||
next_line_block_position: Length,
|
||||
}
|
||||
|
||||
impl InlineFormattingContext {
|
||||
pub(super) fn layout<'a>(
|
||||
&'a self,
|
||||
containing_block: &ContainingBlock,
|
||||
tree_rank: usize,
|
||||
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
|
||||
) -> FlowChildren {
|
||||
let mut ifc = InlineFormattingContextState {
|
||||
containing_block,
|
||||
partial_inline_boxes_stack: Vec::new(),
|
||||
line_boxes: LinesBoxes {
|
||||
boxes: Vec::new(),
|
||||
next_line_block_position: Length::zero(),
|
||||
},
|
||||
inline_position: Length::zero(),
|
||||
current_nesting_level: InlineNestingLevelState {
|
||||
remaining_boxes: self.inline_level_boxes.iter(),
|
||||
fragments_so_far: Vec::with_capacity(self.inline_level_boxes.len()),
|
||||
inline_start: Length::zero(),
|
||||
max_block_size_of_fragments_so_far: Length::zero(),
|
||||
},
|
||||
};
|
||||
loop {
|
||||
if let Some(child) = ifc.current_nesting_level.remaining_boxes.next() {
|
||||
match &**child {
|
||||
InlineLevelBox::InlineBox(inline) => {
|
||||
let partial = inline.start_layout(&mut ifc);
|
||||
ifc.partial_inline_boxes_stack.push(partial)
|
||||
}
|
||||
InlineLevelBox::TextRun(run) => run.layout(&mut ifc),
|
||||
InlineLevelBox::Atomic { style: _, contents } => {
|
||||
// FIXME
|
||||
match *contents {}
|
||||
}
|
||||
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => {
|
||||
let initial_start_corner = match box_.style.specified_display {
|
||||
Display::GeneratingBox(DisplayGeneratingBox::OutsideInside {
|
||||
outside,
|
||||
inside: _,
|
||||
}) => Vec2 {
|
||||
inline: match outside {
|
||||
DisplayOutside::Inline => ifc.inline_position,
|
||||
DisplayOutside::Block => Length::zero(),
|
||||
},
|
||||
block: ifc.line_boxes.next_line_block_position,
|
||||
},
|
||||
Display::Contents => {
|
||||
panic!("display:contents does not generate an abspos box")
|
||||
}
|
||||
Display::None => panic!("display:none does not generate an abspos box"),
|
||||
};
|
||||
absolutely_positioned_fragments
|
||||
.push(box_.layout(initial_start_corner, tree_rank));
|
||||
}
|
||||
InlineLevelBox::OutOfFlowFloatBox(_box_) => {
|
||||
// TODO
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else
|
||||
// Reached the end of ifc.remaining_boxes
|
||||
if let Some(mut partial) = ifc.partial_inline_boxes_stack.pop() {
|
||||
partial.finish_layout(
|
||||
&mut ifc.current_nesting_level,
|
||||
&mut ifc.inline_position,
|
||||
false,
|
||||
);
|
||||
ifc.current_nesting_level = partial.parent_nesting_level
|
||||
} else {
|
||||
ifc.line_boxes
|
||||
.finish_line(&mut ifc.current_nesting_level, containing_block);
|
||||
return FlowChildren {
|
||||
fragments: ifc.line_boxes.boxes,
|
||||
block_size: ifc.line_boxes.next_line_block_position,
|
||||
collapsible_margins_in_children: CollapsedBlockMargins::zero(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LinesBoxes {
|
||||
fn finish_line(
|
||||
&mut self,
|
||||
top_nesting_level: &mut InlineNestingLevelState,
|
||||
containing_block: &ContainingBlock,
|
||||
) {
|
||||
let start_corner = Vec2 {
|
||||
inline: Length::zero(),
|
||||
block: self.next_line_block_position,
|
||||
};
|
||||
let size = Vec2 {
|
||||
inline: containing_block.inline_size,
|
||||
block: std::mem::replace(
|
||||
&mut top_nesting_level.max_block_size_of_fragments_so_far,
|
||||
Length::zero(),
|
||||
),
|
||||
};
|
||||
self.next_line_block_position += size.block;
|
||||
self.boxes.push(Fragment::Anonymous(AnonymousFragment {
|
||||
children: take(&mut top_nesting_level.fragments_so_far),
|
||||
rect: Rect { start_corner, size },
|
||||
mode: containing_block.mode,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl InlineBox {
|
||||
fn start_layout<'box_tree>(
|
||||
&'box_tree self,
|
||||
ifc: &mut InlineFormattingContextState<'box_tree, '_>,
|
||||
) -> PartialInlineBoxFragment<'box_tree> {
|
||||
let style = self.style.clone();
|
||||
let cbis = ifc.containing_block.inline_size;
|
||||
let mut padding = style.padding().percentages_relative_to(cbis);
|
||||
let mut border = style.border_width().percentages_relative_to(cbis);
|
||||
let mut margin = style
|
||||
.margin()
|
||||
.percentages_relative_to(cbis)
|
||||
.auto_is(Length::zero);
|
||||
if self.first_fragment {
|
||||
ifc.inline_position += padding.inline_start + border.inline_start + margin.inline_start;
|
||||
} else {
|
||||
padding.inline_start = Length::zero();
|
||||
border.inline_start = Length::zero();
|
||||
margin.inline_start = Length::zero();
|
||||
}
|
||||
let mut start_corner = Vec2 {
|
||||
block: padding.block_start + border.block_start + margin.block_start,
|
||||
inline: ifc.inline_position - ifc.current_nesting_level.inline_start,
|
||||
};
|
||||
start_corner += &relative_adjustement(
|
||||
&style,
|
||||
ifc.containing_block.inline_size,
|
||||
ifc.containing_block.block_size,
|
||||
);
|
||||
PartialInlineBoxFragment {
|
||||
style,
|
||||
start_corner,
|
||||
padding,
|
||||
border,
|
||||
margin,
|
||||
last_box_tree_fragment: self.last_fragment,
|
||||
parent_nesting_level: std::mem::replace(
|
||||
&mut ifc.current_nesting_level,
|
||||
InlineNestingLevelState {
|
||||
remaining_boxes: self.children.iter(),
|
||||
fragments_so_far: Vec::with_capacity(self.children.len()),
|
||||
inline_start: ifc.inline_position,
|
||||
max_block_size_of_fragments_so_far: Length::zero(),
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'box_tree> PartialInlineBoxFragment<'box_tree> {
|
||||
fn finish_layout(
|
||||
&mut self,
|
||||
nesting_level: &mut InlineNestingLevelState,
|
||||
inline_position: &mut Length,
|
||||
at_line_break: bool,
|
||||
) {
|
||||
let mut fragment = BoxFragment {
|
||||
style: self.style.clone(),
|
||||
children: take(&mut nesting_level.fragments_so_far),
|
||||
content_rect: Rect {
|
||||
size: Vec2 {
|
||||
inline: *inline_position - self.start_corner.inline,
|
||||
block: nesting_level.max_block_size_of_fragments_so_far,
|
||||
},
|
||||
start_corner: self.start_corner.clone(),
|
||||
},
|
||||
padding: self.padding.clone(),
|
||||
border: self.border.clone(),
|
||||
margin: self.margin.clone(),
|
||||
block_margins_collapsed_with_children: CollapsedBlockMargins::zero(),
|
||||
};
|
||||
let last_fragment = self.last_box_tree_fragment && !at_line_break;
|
||||
if last_fragment {
|
||||
*inline_position += fragment.padding.inline_end
|
||||
+ fragment.border.inline_end
|
||||
+ fragment.margin.inline_end;
|
||||
} else {
|
||||
fragment.padding.inline_end = Length::zero();
|
||||
fragment.border.inline_end = Length::zero();
|
||||
fragment.margin.inline_end = Length::zero();
|
||||
}
|
||||
self.parent_nesting_level
|
||||
.max_block_size_of_fragments_so_far
|
||||
.max_assign(
|
||||
fragment.content_rect.size.block
|
||||
+ fragment.padding.block_sum()
|
||||
+ fragment.border.block_sum()
|
||||
+ fragment.margin.block_sum(),
|
||||
);
|
||||
self.parent_nesting_level
|
||||
.fragments_so_far
|
||||
.push(Fragment::Box(fragment));
|
||||
}
|
||||
}
|
||||
|
||||
impl TextRun {
|
||||
fn layout(&self, ifc: &mut InlineFormattingContextState) {
|
||||
let available = ifc.containing_block.inline_size - ifc.inline_position;
|
||||
let mut chars = self.text.chars();
|
||||
loop {
|
||||
let mut shaped = ShapedSegment::new_with_naive_shaping(BITSTREAM_VERA_SANS.clone());
|
||||
let mut last_break_opportunity = None;
|
||||
loop {
|
||||
let next = chars.next();
|
||||
if matches!(next, Some(' ') | None) {
|
||||
let inline_size = self.parent_style.font.font_size * shaped.advance_width;
|
||||
if inline_size > available {
|
||||
if let Some((state, iter)) = last_break_opportunity.take() {
|
||||
shaped.restore(&state);
|
||||
chars = iter;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(ch) = next {
|
||||
if ch == ' ' {
|
||||
last_break_opportunity = Some((shaped.save(), chars.clone()))
|
||||
}
|
||||
shaped.append_char(ch).unwrap()
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let inline_size = self.parent_style.font.font_size * shaped.advance_width;
|
||||
// https://www.w3.org/TR/CSS2/visudet.html#propdef-line-height
|
||||
// 'normal':
|
||||
// “set the used value to a "reasonable" value based on the font of the element.”
|
||||
let line_height = self.parent_style.font.font_size.0 * 1.2;
|
||||
let content_rect = Rect {
|
||||
start_corner: Vec2 {
|
||||
block: Length::zero(),
|
||||
inline: ifc.inline_position - ifc.current_nesting_level.inline_start,
|
||||
},
|
||||
size: Vec2 {
|
||||
block: line_height,
|
||||
inline: inline_size,
|
||||
},
|
||||
};
|
||||
ifc.inline_position += inline_size;
|
||||
ifc.current_nesting_level
|
||||
.max_block_size_of_fragments_so_far
|
||||
.max_assign(line_height);
|
||||
ifc.current_nesting_level
|
||||
.fragments_so_far
|
||||
.push(Fragment::Text(TextFragment {
|
||||
parent_style: self.parent_style.clone(),
|
||||
content_rect,
|
||||
text: shaped,
|
||||
}));
|
||||
if chars.as_str().is_empty() {
|
||||
break;
|
||||
} else {
|
||||
// New line
|
||||
ifc.current_nesting_level.inline_start = Length::zero();
|
||||
let mut nesting_level = &mut ifc.current_nesting_level;
|
||||
for partial in ifc.partial_inline_boxes_stack.iter_mut().rev() {
|
||||
partial.finish_layout(nesting_level, &mut ifc.inline_position, true);
|
||||
partial.start_corner.inline = Length::zero();
|
||||
partial.padding.inline_start = Length::zero();
|
||||
partial.border.inline_start = Length::zero();
|
||||
partial.margin.inline_start = Length::zero();
|
||||
partial.parent_nesting_level.inline_start = Length::zero();
|
||||
nesting_level = &mut partial.parent_nesting_level;
|
||||
}
|
||||
ifc.line_boxes
|
||||
.finish_line(nesting_level, ifc.containing_block);
|
||||
ifc.inline_position = Length::zero();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
439
components/layout_2020/flow/mod.rs
Normal file
439
components/layout_2020/flow/mod.rs
Normal file
|
@ -0,0 +1,439 @@
|
|||
//! Flow layout, also known as block-and-inline layout.
|
||||
|
||||
use super::*;
|
||||
use rayon::prelude::*;
|
||||
use rayon_croissant::ParallelIteratorExt;
|
||||
|
||||
mod construct;
|
||||
mod float;
|
||||
mod inline;
|
||||
mod root;
|
||||
|
||||
pub(super) use construct::*;
|
||||
pub(super) use float::*;
|
||||
pub(super) use inline::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct BlockFormattingContext {
|
||||
pub contents: BlockContainer,
|
||||
pub contains_floats: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum BlockContainer {
|
||||
BlockLevelBoxes(Vec<Arc<BlockLevelBox>>),
|
||||
InlineFormattingContext(InlineFormattingContext),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum BlockLevelBox {
|
||||
SameFormattingContextBlock {
|
||||
style: Arc<ComputedValues>,
|
||||
contents: BlockContainer,
|
||||
},
|
||||
OutOfFlowAbsolutelyPositionedBox(AbsolutelyPositionedBox),
|
||||
OutOfFlowFloatBox(FloatBox),
|
||||
Independent {
|
||||
style: Arc<ComputedValues>,
|
||||
contents: IndependentFormattingContext,
|
||||
},
|
||||
}
|
||||
|
||||
pub(super) struct FlowChildren {
|
||||
pub fragments: Vec<Fragment>,
|
||||
pub block_size: Length,
|
||||
pub collapsible_margins_in_children: CollapsedBlockMargins,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct CollapsibleWithParentStartMargin(bool);
|
||||
|
||||
impl BlockFormattingContext {
|
||||
pub(super) fn layout<'a>(
|
||||
&'a self,
|
||||
containing_block: &ContainingBlock,
|
||||
tree_rank: usize,
|
||||
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
|
||||
) -> FlowChildren {
|
||||
let mut float_context;
|
||||
let float_context = if self.contains_floats {
|
||||
float_context = FloatContext::new();
|
||||
Some(&mut float_context)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut flow_children = self.contents.layout(
|
||||
containing_block,
|
||||
tree_rank,
|
||||
absolutely_positioned_fragments,
|
||||
float_context,
|
||||
CollapsibleWithParentStartMargin(false),
|
||||
);
|
||||
flow_children.block_size += flow_children.collapsible_margins_in_children.end.solve();
|
||||
flow_children
|
||||
.collapsible_margins_in_children
|
||||
.collapsed_through = false;
|
||||
flow_children.collapsible_margins_in_children.end = CollapsedMargin::zero();
|
||||
flow_children
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockContainer {
|
||||
fn layout<'a>(
|
||||
&'a self,
|
||||
containing_block: &ContainingBlock,
|
||||
tree_rank: usize,
|
||||
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
|
||||
float_context: Option<&mut FloatContext>,
|
||||
collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
|
||||
) -> FlowChildren {
|
||||
match self {
|
||||
BlockContainer::BlockLevelBoxes(child_boxes) => layout_block_level_children(
|
||||
child_boxes,
|
||||
containing_block,
|
||||
tree_rank,
|
||||
absolutely_positioned_fragments,
|
||||
float_context,
|
||||
collapsible_with_parent_start_margin,
|
||||
),
|
||||
BlockContainer::InlineFormattingContext(ifc) => {
|
||||
ifc.layout(containing_block, tree_rank, absolutely_positioned_fragments)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_block_level_children<'a>(
|
||||
child_boxes: &'a [Arc<BlockLevelBox>],
|
||||
containing_block: &ContainingBlock,
|
||||
tree_rank: usize,
|
||||
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
|
||||
float_context: Option<&mut FloatContext>,
|
||||
collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
|
||||
) -> FlowChildren {
|
||||
fn place_block_level_fragment(fragment: &mut Fragment, placement_state: &mut PlacementState) {
|
||||
match fragment {
|
||||
Fragment::Box(fragment) => {
|
||||
let fragment_block_margins = &fragment.block_margins_collapsed_with_children;
|
||||
let fragment_block_size = fragment.padding.block_sum()
|
||||
+ fragment.border.block_sum()
|
||||
+ fragment.content_rect.size.block;
|
||||
|
||||
if placement_state.next_in_flow_margin_collapses_with_parent_start_margin {
|
||||
assert_eq!(placement_state.current_margin.solve(), Length::zero());
|
||||
placement_state
|
||||
.start_margin
|
||||
.adjoin_assign(&fragment_block_margins.start);
|
||||
if fragment_block_margins.collapsed_through {
|
||||
placement_state
|
||||
.start_margin
|
||||
.adjoin_assign(&fragment_block_margins.end);
|
||||
return;
|
||||
}
|
||||
placement_state.next_in_flow_margin_collapses_with_parent_start_margin = false;
|
||||
} else {
|
||||
placement_state
|
||||
.current_margin
|
||||
.adjoin_assign(&fragment_block_margins.start);
|
||||
}
|
||||
fragment.content_rect.start_corner.block += placement_state.current_margin.solve()
|
||||
+ placement_state.current_block_direction_position;
|
||||
if fragment_block_margins.collapsed_through {
|
||||
placement_state
|
||||
.current_margin
|
||||
.adjoin_assign(&fragment_block_margins.end);
|
||||
return;
|
||||
}
|
||||
placement_state.current_block_direction_position +=
|
||||
placement_state.current_margin.solve() + fragment_block_size;
|
||||
placement_state.current_margin = fragment_block_margins.end;
|
||||
}
|
||||
Fragment::Anonymous(fragment) => {
|
||||
// FIXME(nox): Margin collapsing for hypothetical boxes of
|
||||
// abspos elements is probably wrong.
|
||||
assert!(fragment.children.is_empty());
|
||||
assert_eq!(fragment.rect.size.block, Length::zero());
|
||||
fragment.rect.start_corner.block +=
|
||||
placement_state.current_block_direction_position;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
struct PlacementState {
|
||||
next_in_flow_margin_collapses_with_parent_start_margin: bool,
|
||||
start_margin: CollapsedMargin,
|
||||
current_margin: CollapsedMargin,
|
||||
current_block_direction_position: Length,
|
||||
}
|
||||
|
||||
let abspos_so_far = absolutely_positioned_fragments.len();
|
||||
let mut placement_state = PlacementState {
|
||||
next_in_flow_margin_collapses_with_parent_start_margin:
|
||||
collapsible_with_parent_start_margin.0,
|
||||
start_margin: CollapsedMargin::zero(),
|
||||
current_margin: CollapsedMargin::zero(),
|
||||
current_block_direction_position: Length::zero(),
|
||||
};
|
||||
let mut fragments: Vec<_>;
|
||||
if let Some(float_context) = float_context {
|
||||
// Because floats are involved, we do layout for this block formatting context
|
||||
// in tree order without parallelism. This enables mutable access
|
||||
// to a `FloatContext` that tracks every float encountered so far (again in tree order).
|
||||
fragments = child_boxes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(tree_rank, box_)| {
|
||||
let mut fragment = box_.layout(
|
||||
containing_block,
|
||||
tree_rank,
|
||||
absolutely_positioned_fragments,
|
||||
Some(float_context),
|
||||
);
|
||||
place_block_level_fragment(&mut fragment, &mut placement_state);
|
||||
fragment
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
fragments = child_boxes
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.mapfold_reduce_into(
|
||||
absolutely_positioned_fragments,
|
||||
|abspos_fragments, (tree_rank, box_)| {
|
||||
box_.layout(
|
||||
containing_block,
|
||||
tree_rank,
|
||||
abspos_fragments,
|
||||
/* float_context = */ None,
|
||||
)
|
||||
},
|
||||
|left_abspos_fragments, mut right_abspos_fragments| {
|
||||
left_abspos_fragments.append(&mut right_abspos_fragments);
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
for fragment in &mut fragments {
|
||||
place_block_level_fragment(fragment, &mut placement_state)
|
||||
}
|
||||
}
|
||||
|
||||
adjust_static_positions(
|
||||
&mut absolutely_positioned_fragments[abspos_so_far..],
|
||||
&mut fragments,
|
||||
tree_rank,
|
||||
);
|
||||
|
||||
FlowChildren {
|
||||
fragments,
|
||||
block_size: placement_state.current_block_direction_position,
|
||||
collapsible_margins_in_children: CollapsedBlockMargins {
|
||||
collapsed_through: placement_state
|
||||
.next_in_flow_margin_collapses_with_parent_start_margin,
|
||||
start: placement_state.start_margin,
|
||||
end: placement_state.current_margin,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockLevelBox {
|
||||
fn layout<'a>(
|
||||
&'a self,
|
||||
containing_block: &ContainingBlock,
|
||||
tree_rank: usize,
|
||||
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
|
||||
float_context: Option<&mut FloatContext>,
|
||||
) -> Fragment {
|
||||
match self {
|
||||
BlockLevelBox::SameFormattingContextBlock { style, contents } => {
|
||||
Fragment::Box(layout_in_flow_non_replaced_block_level(
|
||||
containing_block,
|
||||
absolutely_positioned_fragments,
|
||||
style,
|
||||
BlockLevelKind::SameFormattingContextBlock,
|
||||
|containing_block, nested_abspos, collapsible_with_parent_start_margin| {
|
||||
contents.layout(
|
||||
containing_block,
|
||||
tree_rank,
|
||||
nested_abspos,
|
||||
float_context,
|
||||
collapsible_with_parent_start_margin,
|
||||
)
|
||||
},
|
||||
))
|
||||
}
|
||||
BlockLevelBox::Independent { style, contents } => match contents.as_replaced() {
|
||||
Ok(replaced) => {
|
||||
// FIXME
|
||||
match *replaced {}
|
||||
}
|
||||
Err(contents) => Fragment::Box(layout_in_flow_non_replaced_block_level(
|
||||
containing_block,
|
||||
absolutely_positioned_fragments,
|
||||
style,
|
||||
BlockLevelKind::EstablishesAnIndependentFormattingContext,
|
||||
|containing_block, nested_abspos, _| {
|
||||
contents.layout(containing_block, tree_rank, nested_abspos)
|
||||
},
|
||||
)),
|
||||
},
|
||||
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => {
|
||||
absolutely_positioned_fragments.push(box_.layout(Vec2::zero(), tree_rank));
|
||||
Fragment::Anonymous(AnonymousFragment::no_op(containing_block.mode))
|
||||
}
|
||||
BlockLevelBox::OutOfFlowFloatBox(_box_) => {
|
||||
// TODO
|
||||
Fragment::Anonymous(AnonymousFragment::no_op(containing_block.mode))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum BlockLevelKind {
|
||||
SameFormattingContextBlock,
|
||||
EstablishesAnIndependentFormattingContext,
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/css2/visudet.html#blockwidth
|
||||
/// https://drafts.csswg.org/css2/visudet.html#normal-block
|
||||
fn layout_in_flow_non_replaced_block_level<'a>(
|
||||
containing_block: &ContainingBlock,
|
||||
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
|
||||
style: &Arc<ComputedValues>,
|
||||
block_level_kind: BlockLevelKind,
|
||||
layout_contents: impl FnOnce(
|
||||
&ContainingBlock,
|
||||
&mut Vec<AbsolutelyPositionedFragment<'a>>,
|
||||
CollapsibleWithParentStartMargin,
|
||||
) -> FlowChildren,
|
||||
) -> BoxFragment {
|
||||
let cbis = containing_block.inline_size;
|
||||
let padding = style.padding().percentages_relative_to(cbis);
|
||||
let border = style.border_width().percentages_relative_to(cbis);
|
||||
let mut computed_margin = style.margin().percentages_relative_to(cbis);
|
||||
let pb = &padding + &border;
|
||||
let box_size = style.box_size();
|
||||
let inline_size = box_size.inline.percentage_relative_to(cbis);
|
||||
if let LengthOrAuto::Length(is) = inline_size {
|
||||
let inline_margins = cbis - is - pb.inline_sum();
|
||||
use LengthOrAuto::*;
|
||||
match (
|
||||
&mut computed_margin.inline_start,
|
||||
&mut computed_margin.inline_end,
|
||||
) {
|
||||
(s @ &mut Auto, e @ &mut Auto) => {
|
||||
*s = Length(inline_margins / 2.);
|
||||
*e = Length(inline_margins / 2.);
|
||||
}
|
||||
(s @ &mut Auto, _) => {
|
||||
*s = Length(inline_margins);
|
||||
}
|
||||
(_, e @ &mut Auto) => {
|
||||
*e = Length(inline_margins);
|
||||
}
|
||||
(_, e @ _) => {
|
||||
// Either the inline-end margin is auto,
|
||||
// or we’re over-constrained and we do as if it were.
|
||||
*e = Length(inline_margins);
|
||||
}
|
||||
}
|
||||
}
|
||||
let margin = computed_margin.auto_is(Length::zero);
|
||||
let mut block_margins_collapsed_with_children = CollapsedBlockMargins::from_margin(&margin);
|
||||
let inline_size = inline_size.auto_is(|| cbis - pb.inline_sum() - margin.inline_sum());
|
||||
let block_size = match box_size.block {
|
||||
LengthOrPercentageOrAuto::Length(l) => LengthOrAuto::Length(l),
|
||||
LengthOrPercentageOrAuto::Percentage(p) => containing_block.block_size.map(|cbbs| cbbs * p),
|
||||
LengthOrPercentageOrAuto::Auto => LengthOrAuto::Auto,
|
||||
};
|
||||
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 this_start_margin_can_collapse_with_children = CollapsibleWithParentStartMargin(
|
||||
block_level_kind == BlockLevelKind::SameFormattingContextBlock
|
||||
&& pb.block_start == Length::zero(),
|
||||
);
|
||||
let this_end_margin_can_collapse_with_children = (block_level_kind, pb.block_end, block_size)
|
||||
== (
|
||||
BlockLevelKind::SameFormattingContextBlock,
|
||||
Length::zero(),
|
||||
LengthOrAuto::Auto,
|
||||
);
|
||||
let mut nested_abspos = vec![];
|
||||
let mut flow_children = layout_contents(
|
||||
&containing_block_for_children,
|
||||
if style.box_.position.is_relatively_positioned() {
|
||||
&mut nested_abspos
|
||||
} else {
|
||||
absolutely_positioned_fragments
|
||||
},
|
||||
this_start_margin_can_collapse_with_children,
|
||||
);
|
||||
if this_start_margin_can_collapse_with_children.0 {
|
||||
block_margins_collapsed_with_children
|
||||
.start
|
||||
.adjoin_assign(&flow_children.collapsible_margins_in_children.start);
|
||||
if flow_children
|
||||
.collapsible_margins_in_children
|
||||
.collapsed_through
|
||||
{
|
||||
block_margins_collapsed_with_children
|
||||
.start
|
||||
.adjoin_assign(&std::mem::replace(
|
||||
&mut flow_children.collapsible_margins_in_children.end,
|
||||
CollapsedMargin::zero(),
|
||||
));
|
||||
}
|
||||
}
|
||||
if this_end_margin_can_collapse_with_children {
|
||||
block_margins_collapsed_with_children
|
||||
.end
|
||||
.adjoin_assign(&flow_children.collapsible_margins_in_children.end);
|
||||
} else {
|
||||
flow_children.block_size += flow_children.collapsible_margins_in_children.end.solve();
|
||||
}
|
||||
block_margins_collapsed_with_children.collapsed_through =
|
||||
this_start_margin_can_collapse_with_children.0
|
||||
&& this_end_margin_can_collapse_with_children
|
||||
&& flow_children
|
||||
.collapsible_margins_in_children
|
||||
.collapsed_through;
|
||||
let relative_adjustement = relative_adjustement(style, inline_size, block_size);
|
||||
let block_size = block_size.auto_is(|| flow_children.block_size);
|
||||
let content_rect = Rect {
|
||||
start_corner: Vec2 {
|
||||
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,
|
||||
},
|
||||
};
|
||||
if style.box_.position.is_relatively_positioned() {
|
||||
AbsolutelyPositionedFragment::in_positioned_containing_block(
|
||||
&nested_abspos,
|
||||
&mut flow_children.fragments,
|
||||
&content_rect.size,
|
||||
&padding,
|
||||
containing_block_for_children.mode,
|
||||
)
|
||||
}
|
||||
BoxFragment {
|
||||
style: style.clone(),
|
||||
children: flow_children.fragments,
|
||||
content_rect,
|
||||
padding,
|
||||
border,
|
||||
margin,
|
||||
block_margins_collapsed_with_children,
|
||||
}
|
||||
}
|
122
components/layout_2020/flow/root.rs
Normal file
122
components/layout_2020/flow/root.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
use super::*;
|
||||
|
||||
impl crate::dom::Document {
|
||||
pub(crate) fn layout(
|
||||
&self,
|
||||
viewport: crate::primitives::Size<crate::primitives::CssPx>,
|
||||
) -> Vec<Fragment> {
|
||||
BoxTreeRoot::construct(self).layout(viewport)
|
||||
}
|
||||
}
|
||||
|
||||
struct BoxTreeRoot(BlockFormattingContext);
|
||||
|
||||
impl BoxTreeRoot {
|
||||
pub fn construct(document: &dom::Document) -> Self {
|
||||
let author_styles = &document.parse_stylesheets();
|
||||
let context = Context {
|
||||
document,
|
||||
author_styles,
|
||||
};
|
||||
let root_element = document.root_element();
|
||||
let style = style_for_element(context.author_styles, context.document, root_element, None);
|
||||
let (contains_floats, boxes) = construct_for_root_element(&context, root_element, style);
|
||||
Self(BlockFormattingContext {
|
||||
contains_floats: contains_floats == ContainsFloats::Yes,
|
||||
contents: BlockContainer::BlockLevelBoxes(boxes),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn construct_for_root_element(
|
||||
context: &Context,
|
||||
root_element: dom::NodeId,
|
||||
style: Arc<ComputedValues>,
|
||||
) -> (ContainsFloats, Vec<Arc<BlockLevelBox>>) {
|
||||
let replaced = ReplacedContent::for_element(root_element, context);
|
||||
|
||||
let display_inside = match style.box_.display {
|
||||
Display::None => return (ContainsFloats::No, Vec::new()),
|
||||
Display::Contents if replaced.is_some() => {
|
||||
// 'display: contents' computes to 'none' for replaced elements
|
||||
return (ContainsFloats::No, Vec::new());
|
||||
}
|
||||
// https://drafts.csswg.org/css-display-3/#transformations
|
||||
Display::Contents => DisplayInside::Flow,
|
||||
// The root element is blockified, ignore DisplayOutside
|
||||
Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside,
|
||||
};
|
||||
|
||||
if let Some(replaced) = replaced {
|
||||
let _box = match replaced {};
|
||||
#[allow(unreachable_code)]
|
||||
{
|
||||
return (ContainsFloats::No, vec![Arc::new(_box)]);
|
||||
}
|
||||
}
|
||||
|
||||
let contents = IndependentFormattingContext::construct(
|
||||
context,
|
||||
&style,
|
||||
display_inside,
|
||||
Contents::OfElement(root_element),
|
||||
);
|
||||
if style.box_.position.is_absolutely_positioned() {
|
||||
(
|
||||
ContainsFloats::No,
|
||||
vec![Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
|
||||
AbsolutelyPositionedBox { style, contents },
|
||||
))],
|
||||
)
|
||||
} else if style.box_.float.is_floating() {
|
||||
(
|
||||
ContainsFloats::Yes,
|
||||
vec![Arc::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox {
|
||||
contents,
|
||||
style,
|
||||
}))],
|
||||
)
|
||||
} else {
|
||||
(
|
||||
ContainsFloats::No,
|
||||
vec![Arc::new(BlockLevelBox::Independent { style, contents })],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxTreeRoot {
|
||||
fn layout(&self, viewport: crate::primitives::Size<crate::primitives::CssPx>) -> Vec<Fragment> {
|
||||
let initial_containing_block_size = Vec2 {
|
||||
inline: Length { px: viewport.width },
|
||||
block: Length {
|
||||
px: viewport.height,
|
||||
},
|
||||
};
|
||||
|
||||
let initial_containing_block = ContainingBlock {
|
||||
inline_size: initial_containing_block_size.inline,
|
||||
block_size: LengthOrAuto::Length(initial_containing_block_size.block),
|
||||
// FIXME: use the document’s mode:
|
||||
// https://drafts.csswg.org/css-writing-modes/#principal-flow
|
||||
mode: (WritingMode::HorizontalTb, Direction::Ltr),
|
||||
};
|
||||
let dummy_tree_rank = 0;
|
||||
let mut absolutely_positioned_fragments = vec![];
|
||||
let mut flow_children = self.0.layout(
|
||||
&initial_containing_block,
|
||||
dummy_tree_rank,
|
||||
&mut absolutely_positioned_fragments,
|
||||
);
|
||||
|
||||
let initial_containing_block = DefiniteContainingBlock {
|
||||
size: initial_containing_block_size,
|
||||
mode: initial_containing_block.mode,
|
||||
};
|
||||
flow_children.fragments.par_extend(
|
||||
absolutely_positioned_fragments
|
||||
.par_iter()
|
||||
.map(|a| a.layout(&initial_containing_block)),
|
||||
);
|
||||
flow_children.fragments
|
||||
}
|
||||
}
|
116
components/layout_2020/fragments.rs
Normal file
116
components/layout_2020/fragments.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use super::*;
|
||||
use crate::text::ShapedSegment;
|
||||
|
||||
pub(crate) enum Fragment {
|
||||
Box(BoxFragment),
|
||||
Anonymous(AnonymousFragment),
|
||||
Text(TextFragment),
|
||||
}
|
||||
|
||||
pub(crate) struct BoxFragment {
|
||||
pub style: Arc<ComputedValues>,
|
||||
pub children: Vec<Fragment>,
|
||||
|
||||
/// From the containing block’s start corner…?
|
||||
/// This might be broken when the containing block is in a different writing mode:
|
||||
/// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
|
||||
pub content_rect: Rect<Length>,
|
||||
|
||||
pub padding: Sides<Length>,
|
||||
pub border: Sides<Length>,
|
||||
pub margin: Sides<Length>,
|
||||
|
||||
pub block_margins_collapsed_with_children: CollapsedBlockMargins,
|
||||
}
|
||||
|
||||
pub(crate) struct CollapsedBlockMargins {
|
||||
pub collapsed_through: bool,
|
||||
pub start: CollapsedMargin,
|
||||
pub end: CollapsedMargin,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct CollapsedMargin {
|
||||
max_positive: Length,
|
||||
min_negative: Length,
|
||||
}
|
||||
|
||||
/// Can contain child fragments with relative coordinates, but does not contribute to painting itself.
|
||||
pub(crate) struct AnonymousFragment {
|
||||
pub rect: Rect<Length>,
|
||||
pub children: Vec<Fragment>,
|
||||
pub mode: (WritingMode, Direction),
|
||||
}
|
||||
|
||||
pub(crate) struct TextFragment {
|
||||
pub parent_style: Arc<ComputedValues>,
|
||||
pub content_rect: Rect<Length>,
|
||||
pub text: ShapedSegment,
|
||||
}
|
||||
|
||||
impl AnonymousFragment {
|
||||
pub fn no_op(mode: (WritingMode, Direction)) -> Self {
|
||||
Self {
|
||||
children: vec![],
|
||||
rect: Rect::zero(),
|
||||
mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxFragment {
|
||||
pub fn border_rect(&self) -> Rect<Length> {
|
||||
self.content_rect
|
||||
.inflate(&self.padding)
|
||||
.inflate(&self.border)
|
||||
}
|
||||
}
|
||||
|
||||
impl CollapsedBlockMargins {
|
||||
pub fn from_margin(margin: &Sides<Length>) -> Self {
|
||||
Self {
|
||||
collapsed_through: false,
|
||||
start: CollapsedMargin::new(margin.block_start),
|
||||
end: CollapsedMargin::new(margin.block_end),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
collapsed_through: false,
|
||||
start: CollapsedMargin::zero(),
|
||||
end: CollapsedMargin::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CollapsedMargin {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
max_positive: Length::zero(),
|
||||
min_negative: Length::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(margin: Length) -> Self {
|
||||
Self {
|
||||
max_positive: margin.max(Length::zero()),
|
||||
min_negative: margin.min(Length::zero()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjoin(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
max_positive: self.max_positive.max(other.max_positive),
|
||||
min_negative: self.min_negative.min(other.min_negative),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjoin_assign(&mut self, other: &Self) {
|
||||
*self = self.adjoin(other);
|
||||
}
|
||||
|
||||
pub fn solve(&self) -> Length {
|
||||
self.max_positive + self.min_negative
|
||||
}
|
||||
}
|
330
components/layout_2020/geom.rs
Normal file
330
components/layout_2020/geom.rs
Normal file
|
@ -0,0 +1,330 @@
|
|||
pub(crate) use crate::style::values::Length;
|
||||
use crate::style::values::{LengthOrAuto, LengthOrPercentage, LengthOrPercentageOrAuto};
|
||||
|
||||
pub(crate) mod physical {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Vec2<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Rect<T> {
|
||||
pub top_left: Vec2<T>,
|
||||
pub size: Vec2<T>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Sides<T> {
|
||||
pub top: T,
|
||||
pub left: T,
|
||||
pub bottom: T,
|
||||
pub right: T,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod flow_relative {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Vec2<T> {
|
||||
pub inline: T,
|
||||
pub block: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Rect<T> {
|
||||
pub start_corner: Vec2<T>,
|
||||
pub size: Vec2<T>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Sides<T> {
|
||||
pub inline_start: T,
|
||||
pub inline_end: T,
|
||||
pub block_start: T,
|
||||
pub block_end: T,
|
||||
}
|
||||
}
|
||||
|
||||
use crate::style::values::{Direction, WritingMode};
|
||||
use std::ops::{Add, AddAssign, Sub};
|
||||
|
||||
impl<T> Add<&'_ physical::Vec2<T>> for &'_ physical::Vec2<T>
|
||||
where
|
||||
T: Add<Output = T> + Copy,
|
||||
{
|
||||
type Output = physical::Vec2<T>;
|
||||
|
||||
fn add(self, other: &'_ physical::Vec2<T>) -> Self::Output {
|
||||
physical::Vec2 {
|
||||
x: self.x + other.x,
|
||||
y: self.y + other.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> physical::Vec2<T> {
|
||||
pub fn size_to_flow_relative(&self, mode: (WritingMode, Direction)) -> flow_relative::Vec2<T> {
|
||||
// https://drafts.csswg.org/css-writing-modes/#logical-to-physical
|
||||
let (i, b) = if let (WritingMode::HorizontalTb, _) = mode {
|
||||
(&self.x, &self.y)
|
||||
} else {
|
||||
(&self.y, &self.x)
|
||||
};
|
||||
flow_relative::Vec2 {
|
||||
inline: i.clone(),
|
||||
block: b.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Add<&'_ flow_relative::Vec2<T>> for &'_ flow_relative::Vec2<T>
|
||||
where
|
||||
T: Add<Output = T> + Copy,
|
||||
{
|
||||
type Output = flow_relative::Vec2<T>;
|
||||
|
||||
fn add(self, other: &'_ flow_relative::Vec2<T>) -> Self::Output {
|
||||
flow_relative::Vec2 {
|
||||
inline: self.inline + other.inline,
|
||||
block: self.block + other.block,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AddAssign<&'_ flow_relative::Vec2<T>> for flow_relative::Vec2<T>
|
||||
where
|
||||
T: AddAssign<T> + Copy,
|
||||
{
|
||||
fn add_assign(&mut self, other: &'_ flow_relative::Vec2<T>) {
|
||||
self.inline += other.inline;
|
||||
self.block += other.block;
|
||||
}
|
||||
}
|
||||
|
||||
impl flow_relative::Vec2<Length> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
inline: Length::zero(),
|
||||
block: Length::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl flow_relative::Sides<Length> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
inline_start: Length::zero(),
|
||||
inline_end: Length::zero(),
|
||||
block_start: Length::zero(),
|
||||
block_end: Length::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl flow_relative::Rect<Length> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
start_corner: flow_relative::Vec2::zero(),
|
||||
size: flow_relative::Vec2::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> flow_relative::Vec2<T> {
|
||||
pub fn size_to_physical(&self, mode: (WritingMode, Direction)) -> physical::Vec2<T> {
|
||||
// https://drafts.csswg.org/css-writing-modes/#logical-to-physical
|
||||
let (x, y) = if let (WritingMode::HorizontalTb, _) = mode {
|
||||
(&self.inline, &self.block)
|
||||
} else {
|
||||
(&self.block, &self.inline)
|
||||
};
|
||||
physical::Vec2 {
|
||||
x: x.clone(),
|
||||
y: y.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<physical::Vec2<Length>> for crate::primitives::Point<crate::primitives::CssPx> {
|
||||
fn from(v: physical::Vec2<Length>) -> Self {
|
||||
crate::primitives::Point::from_lengths(v.x.into(), v.y.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> physical::Sides<T> {
|
||||
pub fn to_flow_relative(&self, mode: (WritingMode, Direction)) -> flow_relative::Sides<T> {
|
||||
use Direction::*;
|
||||
use WritingMode::*;
|
||||
|
||||
// https://drafts.csswg.org/css-writing-modes/#logical-to-physical
|
||||
let (bs, be) = match mode.0 {
|
||||
HorizontalTb => (&self.top, &self.bottom),
|
||||
VerticalRl | SidewaysRl => (&self.right, &self.left),
|
||||
VerticalLr | SidewaysLr => (&self.left, &self.right),
|
||||
};
|
||||
let (is, ie) = match mode {
|
||||
(HorizontalTb, Ltr) => (&self.left, &self.right),
|
||||
(HorizontalTb, Rtl) => (&self.right, &self.left),
|
||||
(VerticalRl, Ltr) | (SidewaysRl, Ltr) | (VerticalLr, Ltr) | (SidewaysLr, Rtl) => {
|
||||
(&self.top, &self.bottom)
|
||||
}
|
||||
(VerticalRl, Rtl) | (SidewaysRl, Rtl) | (VerticalLr, Rtl) | (SidewaysLr, Ltr) => {
|
||||
(&self.bottom, &self.top)
|
||||
}
|
||||
};
|
||||
flow_relative::Sides {
|
||||
inline_start: is.clone(),
|
||||
inline_end: ie.clone(),
|
||||
block_start: bs.clone(),
|
||||
block_end: be.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> flow_relative::Sides<T> {
|
||||
pub fn map<U>(&self, f: impl Fn(&T) -> U) -> flow_relative::Sides<U> {
|
||||
flow_relative::Sides {
|
||||
inline_start: f(&self.inline_start),
|
||||
inline_end: f(&self.inline_end),
|
||||
block_start: f(&self.block_start),
|
||||
block_end: f(&self.block_end),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_inline_and_block_axes<U>(
|
||||
&self,
|
||||
inline_f: impl Fn(&T) -> U,
|
||||
block_f: impl Fn(&T) -> U,
|
||||
) -> flow_relative::Sides<U> {
|
||||
flow_relative::Sides {
|
||||
inline_start: inline_f(&self.inline_start),
|
||||
inline_end: inline_f(&self.inline_end),
|
||||
block_start: block_f(&self.block_start),
|
||||
block_end: block_f(&self.block_end),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inline_sum(&self) -> T::Output
|
||||
where
|
||||
T: Add + Copy,
|
||||
{
|
||||
self.inline_start + self.inline_end
|
||||
}
|
||||
|
||||
pub fn block_sum(&self) -> T::Output
|
||||
where
|
||||
T: Add + Copy,
|
||||
{
|
||||
self.block_start + self.block_end
|
||||
}
|
||||
|
||||
pub fn start_corner(&self) -> flow_relative::Vec2<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
flow_relative::Vec2 {
|
||||
inline: self.inline_start.clone(),
|
||||
block: self.block_start.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl flow_relative::Sides<LengthOrPercentage> {
|
||||
pub fn percentages_relative_to(&self, basis: Length) -> flow_relative::Sides<Length> {
|
||||
self.map(|s| s.percentage_relative_to(basis))
|
||||
}
|
||||
}
|
||||
|
||||
impl flow_relative::Sides<LengthOrPercentageOrAuto> {
|
||||
pub fn percentages_relative_to(&self, basis: Length) -> flow_relative::Sides<LengthOrAuto> {
|
||||
self.map(|s| s.percentage_relative_to(basis))
|
||||
}
|
||||
}
|
||||
|
||||
impl flow_relative::Sides<LengthOrAuto> {
|
||||
pub fn auto_is(&self, f: impl Fn() -> Length) -> flow_relative::Sides<Length> {
|
||||
self.map(|s| s.auto_is(&f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Add<&'_ flow_relative::Sides<T>> for &'_ flow_relative::Sides<T>
|
||||
where
|
||||
T: Add<Output = T> + Copy,
|
||||
{
|
||||
type Output = flow_relative::Sides<T>;
|
||||
|
||||
fn add(self, other: &'_ flow_relative::Sides<T>) -> Self::Output {
|
||||
flow_relative::Sides {
|
||||
inline_start: self.inline_start + other.inline_start,
|
||||
inline_end: self.inline_end + other.inline_end,
|
||||
block_start: self.block_start + other.block_start,
|
||||
block_end: self.block_end + other.block_end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> flow_relative::Rect<T> {
|
||||
pub fn inflate(&self, sides: &flow_relative::Sides<T>) -> Self
|
||||
where
|
||||
T: Add<Output = T> + Copy,
|
||||
T: Sub<Output = T> + Copy,
|
||||
{
|
||||
flow_relative::Rect {
|
||||
start_corner: flow_relative::Vec2 {
|
||||
inline: self.start_corner.inline - sides.inline_start,
|
||||
block: self.start_corner.block - sides.block_start,
|
||||
},
|
||||
size: flow_relative::Vec2 {
|
||||
inline: self.size.inline + sides.inline_sum(),
|
||||
block: self.size.block + sides.block_sum(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_physical(
|
||||
&self,
|
||||
mode: (WritingMode, Direction),
|
||||
// Will be needed for other writing modes
|
||||
// FIXME: what if the containing block has a different mode?
|
||||
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
|
||||
_containing_block: &physical::Rect<T>,
|
||||
) -> physical::Rect<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
// Top-left corner
|
||||
let (tl_x, tl_y) = if let (WritingMode::HorizontalTb, Direction::Ltr) = mode {
|
||||
(&self.start_corner.inline, &self.start_corner.block)
|
||||
} else {
|
||||
unimplemented!()
|
||||
};
|
||||
physical::Rect {
|
||||
top_left: physical::Vec2 {
|
||||
x: tl_x.clone(),
|
||||
y: tl_y.clone(),
|
||||
},
|
||||
size: self.size.size_to_physical(mode),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> physical::Rect<T> {
|
||||
pub fn translate(&self, by: &physical::Vec2<T>) -> Self
|
||||
where
|
||||
T: Add<Output = T> + Copy,
|
||||
{
|
||||
physical::Rect {
|
||||
top_left: &self.top_left + by,
|
||||
size: self.size.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<physical::Rect<Length>> for crate::primitives::Rect<crate::primitives::CssPx> {
|
||||
fn from(r: physical::Rect<Length>) -> Self {
|
||||
crate::primitives::Rect {
|
||||
origin: crate::primitives::Point::new(r.top_left.x.px, r.top_left.y.px),
|
||||
size: crate::primitives::Size::new(r.size.x.px, r.size.y.px),
|
||||
}
|
||||
}
|
||||
}
|
328
components/layout_2020/positioned.rs
Normal file
328
components/layout_2020/positioned.rs
Normal file
|
@ -0,0 +1,328 @@
|
|||
use super::*;
|
||||
use rayon::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct AbsolutelyPositionedBox {
|
||||
pub style: Arc<ComputedValues>,
|
||||
pub contents: IndependentFormattingContext,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct AbsolutelyPositionedFragment<'box_> {
|
||||
absolutely_positioned_box: &'box_ AbsolutelyPositionedBox,
|
||||
|
||||
/// The rank of the child from which this absolutely positioned fragment
|
||||
/// came from, when doing the layout of a block container. Used to compute
|
||||
/// static positions when going up the tree.
|
||||
pub(super) tree_rank: usize,
|
||||
|
||||
pub(super) inline_start: AbsoluteBoxOffsets<LengthOrPercentage>,
|
||||
inline_size: LengthOrPercentageOrAuto,
|
||||
|
||||
pub(super) block_start: AbsoluteBoxOffsets<LengthOrPercentage>,
|
||||
block_size: LengthOrPercentageOrAuto,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(super) enum AbsoluteBoxOffsets<NonStatic> {
|
||||
StaticStart { start: Length },
|
||||
Start { start: NonStatic },
|
||||
End { end: NonStatic },
|
||||
Both { start: NonStatic, end: NonStatic },
|
||||
}
|
||||
|
||||
impl AbsolutelyPositionedBox {
|
||||
pub(super) fn layout<'a>(
|
||||
&'a self,
|
||||
initial_start_corner: Vec2<Length>,
|
||||
tree_rank: usize,
|
||||
) -> AbsolutelyPositionedFragment {
|
||||
let style = &self.style;
|
||||
let box_offsets = style.box_offsets();
|
||||
let box_size = style.box_size();
|
||||
|
||||
let inline_size = box_size.inline;
|
||||
let block_size = box_size.block;
|
||||
|
||||
fn absolute_box_offsets(
|
||||
initial_static_start: Length,
|
||||
start: LengthOrPercentageOrAuto,
|
||||
end: LengthOrPercentageOrAuto,
|
||||
) -> AbsoluteBoxOffsets<LengthOrPercentage> {
|
||||
match (start.non_auto(), end.non_auto()) {
|
||||
(None, None) => AbsoluteBoxOffsets::StaticStart {
|
||||
start: initial_static_start,
|
||||
},
|
||||
(Some(start), Some(end)) => AbsoluteBoxOffsets::Both { start, end },
|
||||
(None, Some(end)) => AbsoluteBoxOffsets::End { end },
|
||||
(Some(start), None) => AbsoluteBoxOffsets::Start { start },
|
||||
}
|
||||
}
|
||||
|
||||
let inline_start = absolute_box_offsets(
|
||||
initial_start_corner.inline,
|
||||
box_offsets.inline_start,
|
||||
box_offsets.inline_end,
|
||||
);
|
||||
let block_start = absolute_box_offsets(
|
||||
initial_start_corner.block,
|
||||
box_offsets.block_start,
|
||||
box_offsets.block_end,
|
||||
);
|
||||
|
||||
AbsolutelyPositionedFragment {
|
||||
absolutely_positioned_box: self,
|
||||
tree_rank,
|
||||
inline_start,
|
||||
inline_size,
|
||||
block_start,
|
||||
block_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AbsolutelyPositionedFragment<'a> {
|
||||
pub(super) fn in_positioned_containing_block(
|
||||
absolute: &[Self],
|
||||
fragments: &mut Vec<Fragment>,
|
||||
content_rect_size: &Vec2<Length>,
|
||||
padding: &Sides<Length>,
|
||||
mode: (WritingMode, Direction),
|
||||
) {
|
||||
if absolute.is_empty() {
|
||||
return;
|
||||
}
|
||||
let padding_rect = Rect {
|
||||
size: content_rect_size.clone(),
|
||||
// Ignore the content rect’s position in its own containing block:
|
||||
start_corner: Vec2::zero(),
|
||||
}
|
||||
.inflate(&padding);
|
||||
let containing_block = DefiniteContainingBlock {
|
||||
size: padding_rect.size.clone(),
|
||||
mode,
|
||||
};
|
||||
fragments.push(Fragment::Anonymous(AnonymousFragment {
|
||||
children: absolute
|
||||
.par_iter()
|
||||
.map(|a| a.layout(&containing_block))
|
||||
.collect(),
|
||||
rect: padding_rect,
|
||||
mode,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) fn layout(&self, containing_block: &DefiniteContainingBlock) -> Fragment {
|
||||
let style = &self.absolutely_positioned_box.style;
|
||||
let cbis = containing_block.size.inline;
|
||||
let cbbs = containing_block.size.block;
|
||||
|
||||
let padding = style.padding().percentages_relative_to(cbis);
|
||||
let border = style.border_width().percentages_relative_to(cbis);
|
||||
let computed_margin = style.margin().percentages_relative_to(cbis);
|
||||
let pb = &padding + &border;
|
||||
|
||||
enum Anchor {
|
||||
Start(Length),
|
||||
End(Length),
|
||||
}
|
||||
|
||||
fn solve_axis(
|
||||
containing_size: Length,
|
||||
padding_border_sum: Length,
|
||||
computed_margin_start: LengthOrAuto,
|
||||
computed_margin_end: LengthOrAuto,
|
||||
solve_margins: impl FnOnce(Length) -> (Length, Length),
|
||||
box_offsets: AbsoluteBoxOffsets<LengthOrPercentage>,
|
||||
size: LengthOrPercentageOrAuto,
|
||||
) -> (Anchor, LengthOrAuto, Length, Length) {
|
||||
let size = size.percentage_relative_to(containing_size);
|
||||
match box_offsets {
|
||||
AbsoluteBoxOffsets::StaticStart { start } => (
|
||||
Anchor::Start(start),
|
||||
size,
|
||||
computed_margin_start.auto_is(Length::zero),
|
||||
computed_margin_end.auto_is(Length::zero),
|
||||
),
|
||||
AbsoluteBoxOffsets::Start { start } => (
|
||||
Anchor::Start(start.percentage_relative_to(containing_size)),
|
||||
size,
|
||||
computed_margin_start.auto_is(Length::zero),
|
||||
computed_margin_end.auto_is(Length::zero),
|
||||
),
|
||||
AbsoluteBoxOffsets::End { end } => (
|
||||
Anchor::End(end.percentage_relative_to(containing_size)),
|
||||
size,
|
||||
computed_margin_start.auto_is(Length::zero),
|
||||
computed_margin_end.auto_is(Length::zero),
|
||||
),
|
||||
AbsoluteBoxOffsets::Both { start, end } => {
|
||||
let start = start.percentage_relative_to(containing_size);
|
||||
let end = end.percentage_relative_to(containing_size);
|
||||
|
||||
let mut margin_start = computed_margin_start.auto_is(Length::zero);
|
||||
let mut margin_end = computed_margin_end.auto_is(Length::zero);
|
||||
|
||||
let size = if let LengthOrAuto::Length(size) = size {
|
||||
use LengthOrAuto::Auto;
|
||||
let margins = containing_size - start - end - padding_border_sum - size;
|
||||
match (computed_margin_start, computed_margin_end) {
|
||||
(Auto, Auto) => {
|
||||
let (s, e) = solve_margins(margins);
|
||||
margin_start = s;
|
||||
margin_end = e;
|
||||
}
|
||||
(Auto, LengthOrAuto::Length(end)) => {
|
||||
margin_start = margins - end;
|
||||
}
|
||||
(LengthOrAuto::Length(start), Auto) => {
|
||||
margin_end = margins - start;
|
||||
}
|
||||
(LengthOrAuto::Length(_), LengthOrAuto::Length(_)) => {}
|
||||
}
|
||||
size
|
||||
} else {
|
||||
// FIXME(nox): What happens if that is negative?
|
||||
containing_size
|
||||
- start
|
||||
- end
|
||||
- padding_border_sum
|
||||
- margin_start
|
||||
- margin_end
|
||||
};
|
||||
(
|
||||
Anchor::Start(start),
|
||||
LengthOrAuto::Length(size),
|
||||
margin_start,
|
||||
margin_end,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (inline_anchor, inline_size, margin_inline_start, margin_inline_end) = solve_axis(
|
||||
cbis,
|
||||
pb.inline_sum(),
|
||||
computed_margin.inline_start,
|
||||
computed_margin.inline_end,
|
||||
|margins| {
|
||||
if margins.px >= 0. {
|
||||
(margins / 2., margins / 2.)
|
||||
} else {
|
||||
(Length::zero(), margins)
|
||||
}
|
||||
},
|
||||
self.inline_start,
|
||||
self.inline_size,
|
||||
);
|
||||
|
||||
let (block_anchor, block_size, margin_block_start, margin_block_end) = solve_axis(
|
||||
cbis,
|
||||
pb.block_sum(),
|
||||
computed_margin.block_start,
|
||||
computed_margin.block_end,
|
||||
|margins| (margins / 2., margins / 2.),
|
||||
self.block_start,
|
||||
self.block_size,
|
||||
);
|
||||
|
||||
let margin = Sides {
|
||||
inline_start: margin_inline_start,
|
||||
inline_end: margin_inline_end,
|
||||
block_start: margin_block_start,
|
||||
block_end: margin_block_end,
|
||||
};
|
||||
|
||||
let inline_size = inline_size.auto_is(|| {
|
||||
let available_size = match inline_anchor {
|
||||
Anchor::Start(start) => cbis - start - pb.inline_sum() - margin.inline_sum(),
|
||||
Anchor::End(end) => cbis - end - pb.inline_sum() - margin.inline_sum(),
|
||||
};
|
||||
|
||||
// FIXME(nox): shrink-to-fit.
|
||||
available_size
|
||||
});
|
||||
|
||||
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;
|
||||
let mut absolutely_positioned_fragments = vec![];
|
||||
let mut flow_children = self.absolutely_positioned_box.contents.layout(
|
||||
&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,
|
||||
Anchor::End(end) => cbbs - end - pb.inline_end - margin.inline_end - inline_size,
|
||||
};
|
||||
|
||||
let block_size = block_size.auto_is(|| flow_children.block_size);
|
||||
let block_start = match block_anchor {
|
||||
Anchor::Start(start) => start + pb.block_start + margin.block_start,
|
||||
Anchor::End(end) => cbbs - end - pb.block_end - margin.block_end - block_size,
|
||||
};
|
||||
|
||||
let content_rect = Rect {
|
||||
start_corner: Vec2 {
|
||||
inline: inline_start,
|
||||
block: block_start,
|
||||
},
|
||||
size: Vec2 {
|
||||
inline: inline_size,
|
||||
block: block_size,
|
||||
},
|
||||
};
|
||||
|
||||
AbsolutelyPositionedFragment::in_positioned_containing_block(
|
||||
&absolutely_positioned_fragments,
|
||||
&mut flow_children.fragments,
|
||||
&content_rect.size,
|
||||
&padding,
|
||||
containing_block_for_children.mode,
|
||||
);
|
||||
|
||||
Fragment::Box(BoxFragment {
|
||||
style: style.clone(),
|
||||
children: flow_children.fragments,
|
||||
content_rect,
|
||||
padding,
|
||||
border,
|
||||
margin,
|
||||
block_margins_collapsed_with_children: CollapsedBlockMargins::zero(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn adjust_static_positions(
|
||||
absolutely_positioned_fragments: &mut [AbsolutelyPositionedFragment],
|
||||
child_fragments: &mut [Fragment],
|
||||
tree_rank_in_parent: usize,
|
||||
) {
|
||||
for abspos_fragment in absolutely_positioned_fragments {
|
||||
let child_fragment_rect = match &child_fragments[abspos_fragment.tree_rank] {
|
||||
Fragment::Box(b) => &b.content_rect,
|
||||
Fragment::Anonymous(a) => &a.rect,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
abspos_fragment.tree_rank = tree_rank_in_parent;
|
||||
|
||||
if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.inline_start {
|
||||
*start += child_fragment_rect.start_corner.inline;
|
||||
}
|
||||
|
||||
if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.block_start {
|
||||
*start += child_fragment_rect.start_corner.block;
|
||||
}
|
||||
}
|
||||
}
|
28
components/layout_2020/primitives.rs
Normal file
28
components/layout_2020/primitives.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use crate::text;
|
||||
|
||||
/// Origin at top-left corner, unit `1px`
|
||||
pub struct CssPx;
|
||||
|
||||
pub use euclid::point2 as point;
|
||||
pub use euclid::rect;
|
||||
pub type Length<U> = euclid::Length<f32, U>;
|
||||
pub type Point<U> = euclid::TypedPoint2D<f32, U>;
|
||||
pub type Size<U> = euclid::TypedSize2D<f32, U>;
|
||||
pub type Rect<U> = euclid::TypedRect<f32, U>;
|
||||
pub type SideOffsets<U> = euclid::TypedSideOffsets2D<f32, U>;
|
||||
pub type Scale<Src, Dest> = euclid::TypedScale<f32, Src, Dest>;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct RGBA(pub f32, pub f32, pub f32, pub f32);
|
||||
|
||||
pub struct TextRun<'a> {
|
||||
pub segment: &'a text::ShapedSegment,
|
||||
pub font_size: Length<CssPx>,
|
||||
pub origin: Point<CssPx>,
|
||||
}
|
||||
|
||||
impl From<cssparser::RGBA> for RGBA {
|
||||
fn from(c: cssparser::RGBA) -> Self {
|
||||
RGBA(c.red_f32(), c.green_f32(), c.blue_f32(), c.alpha_f32())
|
||||
}
|
||||
}
|
14
components/layout_2020/replaced.rs
Normal file
14
components/layout_2020/replaced.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use super::*;
|
||||
use crate::dom::NodeId;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum ReplacedContent {
|
||||
// Not implemented yet
|
||||
}
|
||||
|
||||
impl ReplacedContent {
|
||||
pub fn for_element(_element: NodeId, _context: &Context) -> Option<Self> {
|
||||
// FIXME: implement <img> etc.
|
||||
None
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue