Auto merge of #24165 - nox:victor, r=SimonSapin

Import victor's layout system 🍷

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/24165)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2019-09-11 04:39:53 -04:00 committed by GitHub
commit 86ad84198e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 3327 additions and 243 deletions

View file

@ -6,6 +6,7 @@ Agoston Szepessy <agszepp@gmail.com>
Alexander Popiak <alex@zenguard.org> <alexander.popiak@gmail.com>
Alexandrov Sergey <splavgm@gmail.com>
Antonio de Luna <tonydelun@gmail.com>
Anthony Ramine <nox@nox.paris> <n.oxyde@gmail.com>
Askeing Yen (fyen) <askeing@gmail.com>
Attila Dusnoki <dati91@gmail.com> <dati91@users.noreply.github.com>
bd339 <bd339@tuta.io> <bd339@localhost.localdomain>

20
Cargo.lock generated
View file

@ -2321,6 +2321,8 @@ name = "layout_2020"
version = "0.0.1"
dependencies = [
"app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.25.8 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx 0.0.1",
@ -2328,9 +2330,11 @@ dependencies = [
"ipc-channel 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"malloc_size_of 0.0.1",
"matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"msg 0.0.1",
"range 0.0.1",
"rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon_croissant 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1",
"script_traits 0.0.1",
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2902,6 +2906,11 @@ name = "mitochondria"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "moite_moite"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "mozangle"
version = "0.2.5"
@ -3693,6 +3702,15 @@ dependencies = [
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon_croissant"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"moite_moite 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rdrand"
version = "0.4.0"
@ -6053,6 +6071,7 @@ dependencies = [
"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
"checksum mitochondria 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9de3eca27871df31c33b807f834b94ef7d000956f57aa25c5aed9c5f0aae8f6f"
"checksum moite_moite 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb5a94c61e12e2cfc16ee3e2b6eca8f126a43c888586626337544a7e824a1af"
"checksum mozangle 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0643469d6622a4ffae6a74f13e78a32bfa1406fe8a63010b3fbf2946797fdac7"
"checksum mozjs 0.12.0 (git+https://github.com/servo/rust-mozjs)" = "<none>"
"checksum mozjs_sys 0.67.0 (git+https://github.com/servo/mozjs?rev=2bf4912a6ff1f5ea333afb1cb53dfe9361157b14)" = "<none>"
@ -6123,6 +6142,7 @@ dependencies = [
"checksum raqote 0.6.2-alpha.0 (git+https://github.com/jrmuizel/raqote)" = "<none>"
"checksum rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83a27732a533a1be0a0035a111fe76db89ad312f6f0347004c220c57f209a123"
"checksum rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98dcf634205083b17d0861252431eb2acbfb698ab7478a2d20de07954f47ec7b"
"checksum rayon_croissant 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5b725e815f3aa08718063883a75003336889debafe2f8fa67fbe91563ddc4efa"
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
"checksum redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "29dbdfd4b9df8ab31dec47c6087b7b13cbf4a776f335e4de8efba8288dda075b"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"

View file

@ -502,7 +502,7 @@ pub fn style_length(style_length: Size, container_size: Option<Au>) -> MaybeAuto
match style_length {
Size::Auto => MaybeAuto::Auto,
Size::LengthPercentage(ref lp) => {
MaybeAuto::from_option(lp.0.maybe_to_used_value(container_size))
MaybeAuto::from_option(lp.0.maybe_to_used_value(container_size.map(|l| l.into())))
},
}
}

View file

@ -14,6 +14,8 @@ doctest = false
[dependencies]
app_units = "0.7"
atomic_refcell = "0.1"
cssparser = "0.25"
euclid = "0.20"
fnv = "1.0"
gfx = {path = "../gfx"}
@ -21,13 +23,15 @@ gfx_traits = {path = "../gfx_traits"}
ipc-channel = "0.12"
libc = "0.2"
malloc_size_of = { path = "../malloc_size_of" }
matches = "0.1"
msg = {path = "../msg"}
range = {path = "../range"}
rayon = "1"
rayon_croissant = "0.1.1"
script_layout_interface = {path = "../script_layout_interface"}
script_traits = {path = "../script_traits"}
serde = "1.0"
servo_arc = {path = "../servo_arc"}
servo_arc = { path = "../servo_arc" }
servo_url = {path = "../url"}
style = {path = "../style", features = ["servo", "servo-layout-2020"]}
style_traits = {path = "../style_traits"}

View file

@ -2,12 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use gfx::font_cache_thread::FontCacheThread;
use msg::constellation_msg::PipelineId;
use std::sync::Mutex;
use style::context::SharedStyleContext;
pub struct LayoutContext<'a> {
pub id: PipelineId,
pub style_context: SharedStyleContext<'a>,
pub font_cache_thread: Mutex<FontCacheThread>,
}
impl<'a> LayoutContext<'a> {

View file

@ -2,17 +2,21 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::element_data::LayoutDataForElement;
use atomic_refcell::AtomicRefCell;
use script_layout_interface::StyleData;
#[repr(C)]
pub struct StyleAndLayoutData {
pub style_data: StyleData,
pub(super) layout_data: AtomicRefCell<LayoutDataForElement>,
}
impl StyleAndLayoutData {
pub fn new() -> Self {
Self {
style_data: StyleData::new(),
layout_data: Default::default(),
}
}
}

View file

@ -0,0 +1,382 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::element_data::{LayoutBox, LayoutDataForElement};
use crate::replaced::ReplacedContent;
use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside, DisplayOutside};
use crate::wrapper::GetRawData;
use atomic_refcell::AtomicRefMut;
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use servo_arc::Arc;
use std::convert::TryInto;
use style::context::SharedStyleContext;
use style::dom::TNode;
use style::properties::ComputedValues;
#[derive(Clone, Copy)]
pub(super) enum WhichPseudoElement {
Before,
After,
}
pub(super) enum Contents<Node> {
/// Refers to a DOM subtree, plus `::before` and `::after` pseudo-elements.
OfElement(Node),
/// 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<Node> {
OfElement(Node),
OfPseudoElement(Vec<PseudoElementContentItem>),
}
pub(super) enum PseudoElementContentItem {
Text(String),
Replaced(ReplacedContent),
}
pub(super) trait TraversalHandler<Node> {
fn handle_text(&mut self, text: String, parent_style: &Arc<ComputedValues>);
/// Or pseudo-element
fn handle_element(
&mut self,
style: &Arc<ComputedValues>,
display: DisplayGeneratingBox,
contents: Contents<Node>,
box_slot: BoxSlot,
);
}
fn traverse_children_of<'dom, Node>(
parent_element: Node,
context: &SharedStyleContext,
handler: &mut impl TraversalHandler<Node>,
) where
Node: NodeExt<'dom>,
{
traverse_pseudo_element(WhichPseudoElement::Before, parent_element, context, handler);
let mut next = parent_element.first_child();
while let Some(child) = next {
if let Some(contents) = child.as_text() {
handler.handle_text(contents, &child.style(context));
} else if child.is_element() {
traverse_element(child, context, handler);
}
next = child.next_sibling();
}
traverse_pseudo_element(WhichPseudoElement::After, parent_element, context, handler);
}
fn traverse_element<'dom, Node>(
element: Node,
context: &SharedStyleContext,
handler: &mut impl TraversalHandler<Node>,
) where
Node: NodeExt<'dom>,
{
let style = element.style(context);
match Display::from(style.get_box().display) {
Display::None => element.unset_boxes_in_subtree(),
Display::Contents => {
if ReplacedContent::for_element(element, context).is_some() {
// `display: content` on a replaced element computes to `display: none`
// <https://drafts.csswg.org/css-display-3/#valdef-display-contents>
element.unset_boxes_in_subtree()
} else {
element.layout_data_mut().self_box = Some(LayoutBox::DisplayContents);
traverse_children_of(element, context, handler)
}
},
Display::GeneratingBox(display) => {
handler.handle_element(
&style,
display,
match ReplacedContent::for_element(element, context) {
Some(replaced) => Contents::Replaced(replaced),
None => Contents::OfElement(element),
},
element.element_box_slot(),
);
},
}
}
fn traverse_pseudo_element<'dom, Node>(
which: WhichPseudoElement,
element: Node,
context: &SharedStyleContext,
handler: &mut impl TraversalHandler<Node>,
) where
Node: NodeExt<'dom>,
{
if let Some(style) = pseudo_element_style(which, element, context) {
match Display::from(style.get_box().display) {
Display::None => element.unset_pseudo_element_box(which),
Display::Contents => {
element.unset_pseudo_element_box(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 = element.pseudo_element_box_slot(which);
handler.handle_element(&style, display, contents, box_slot);
},
}
}
}
fn traverse_pseudo_element_contents<'dom, Node>(
pseudo_element_style: &Arc<ComputedValues>,
items: Vec<PseudoElementContentItem>,
handler: &mut impl TraversalHandler<Node>,
) where
Node: '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) => {
// FIXME
// 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 dont keep pointers to boxes generated by contents of pseudo-elements
// BoxSlot::dummy(),
// )
},
}
}
}
impl<Node> std::convert::TryFrom<Contents<Node>> for NonReplacedContents<Node> {
type Error = ReplacedContent;
fn try_from(contents: Contents<Node>) -> Result<Self, Self::Error> {
match contents {
Contents::OfElement(node) => Ok(NonReplacedContents::OfElement(node)),
Contents::OfPseudoElement(items) => Ok(NonReplacedContents::OfPseudoElement(items)),
Contents::Replaced(replaced) => Err(replaced),
}
}
}
impl<Node> std::convert::From<NonReplacedContents<Node>> for Contents<Node> {
fn from(contents: NonReplacedContents<Node>) -> Self {
match contents {
NonReplacedContents::OfElement(node) => Contents::OfElement(node),
NonReplacedContents::OfPseudoElement(items) => Contents::OfPseudoElement(items),
}
}
}
impl<'dom, Node> NonReplacedContents<Node>
where
Node: NodeExt<'dom>,
{
pub(crate) fn traverse(
self,
inherited_style: &Arc<ComputedValues>,
context: &SharedStyleContext,
handler: &mut impl TraversalHandler<Node>,
) {
match self {
NonReplacedContents::OfElement(node) => traverse_children_of(node, context, handler),
NonReplacedContents::OfPseudoElement(items) => {
traverse_pseudo_element_contents(inherited_style, items, handler)
},
}
}
}
fn pseudo_element_style<'dom, Node>(
_which: WhichPseudoElement,
_element: Node,
_context: &SharedStyleContext,
) -> Option<Arc<ComputedValues>>
where
Node: NodeExt<'dom>,
{
// 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<'dom, Node>(
_pseudo_element_style: &ComputedValues,
_element: Node,
_context: &SharedStyleContext,
) -> Vec<PseudoElementContentItem>
where
Node: NodeExt<'dom>,
{
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")
}
}
}
pub(crate) trait NodeExt<'dom>: 'dom + Copy + Send + Sync {
fn is_element(self) -> bool;
fn as_text(self) -> Option<String>;
fn first_child(self) -> Option<Self>;
fn next_sibling(self) -> Option<Self>;
fn parent_node(self) -> Option<Self>;
fn style(self, context: &SharedStyleContext) -> Arc<ComputedValues>;
fn layout_data_mut(&self) -> AtomicRefMut<LayoutDataForElement>;
fn element_box_slot(&self) -> BoxSlot;
fn pseudo_element_box_slot(&self, which: WhichPseudoElement) -> BoxSlot;
fn unset_pseudo_element_box(self, which: WhichPseudoElement);
fn unset_boxes_in_subtree(self);
}
impl<'dom, T> NodeExt<'dom> for T
where
T: 'dom + LayoutNode + Send + Sync,
{
fn is_element(self) -> bool {
self.to_threadsafe().as_element().is_some()
}
fn as_text(self) -> Option<String> {
if self.is_text_node() {
Some(self.to_threadsafe().node_text_content())
} else {
None
}
}
fn first_child(self) -> Option<Self> {
TNode::first_child(&self)
}
fn next_sibling(self) -> Option<Self> {
TNode::next_sibling(&self)
}
fn parent_node(self) -> Option<Self> {
TNode::next_sibling(&self)
}
fn style(self, context: &SharedStyleContext) -> Arc<ComputedValues> {
self.to_threadsafe().style(context)
}
fn layout_data_mut(&self) -> AtomicRefMut<LayoutDataForElement> {
self.get_raw_data()
.map(|d| d.layout_data.borrow_mut())
.unwrap()
}
fn element_box_slot(&self) -> BoxSlot {
BoxSlot::new(AtomicRefMut::map(self.layout_data_mut(), |data| {
&mut data.self_box
}))
}
fn pseudo_element_box_slot(&self, which: WhichPseudoElement) -> BoxSlot {
BoxSlot::new(AtomicRefMut::map(self.layout_data_mut(), |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, which: WhichPseudoElement) {
if let Some(pseudos) = &mut self.layout_data_mut().pseudo_elements {
match which {
WhichPseudoElement::Before => pseudos.before = None,
WhichPseudoElement::After => pseudos.after = None,
}
}
}
fn unset_boxes_in_subtree(self) {
let mut node = self;
loop {
if node.is_element() {
let traverse_children = {
let mut layout_data = node.layout_data_mut();
layout_data.pseudo_elements = None;
layout_data.self_box.take().is_some()
};
if traverse_children {
// Only descend into children if we removed a box.
// If there wasnt one, then descendants dont have boxes either.
if let Some(child) = node.first_child() {
node = child;
continue;
}
}
}
let mut next_is_a_sibling_of = node;
node = loop {
if let Some(sibling) = next_is_a_sibling_of.next_sibling() {
break sibling;
} else {
next_is_a_sibling_of = node
.parent_node()
.expect("reached the root while traversing only a subtree");
}
};
if next_is_a_sibling_of == self {
// Dont go outside the subtree.
return;
}
}
}
}

View file

@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::flow::inline::InlineLevelBox;
use crate::flow::BlockLevelBox;
use servo_arc::Arc;
#[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>),
}

View file

@ -0,0 +1,665 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::dom_traversal::{BoxSlot, Contents, NodeExt, NonReplacedContents, TraversalHandler};
use crate::element_data::LayoutBox;
use crate::flow::float::FloatBox;
use crate::flow::inline::{InlineBox, InlineFormattingContext, InlineLevelBox, TextRun};
use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
use crate::positioned::AbsolutelyPositionedBox;
use crate::style_ext::{DisplayGeneratingBox, DisplayInside, DisplayOutside};
use crate::IndependentFormattingContext;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use rayon_croissant::ParallelIteratorExt;
use servo_arc::Arc;
use std::convert::TryInto;
use style::context::SharedStyleContext;
use style::properties::ComputedValues;
impl BlockFormattingContext {
pub fn construct<'dom>(
context: &SharedStyleContext<'_>,
style: &Arc<ComputedValues>,
contents: NonReplacedContents<impl NodeExt<'dom>>,
) -> Self {
let (contents, contains_floats) = BlockContainer::construct(context, style, contents);
Self {
contents,
contains_floats: contains_floats == ContainsFloats::Yes,
}
}
}
enum IntermediateBlockLevelBox<Node> {
SameFormattingContextBlock {
style: Arc<ComputedValues>,
contents: IntermediateBlockContainer<Node>,
},
Independent {
style: Arc<ComputedValues>,
display_inside: DisplayInside,
contents: Contents<Node>,
},
OutOfFlowAbsolutelyPositionedBox {
style: Arc<ComputedValues>,
display_inside: DisplayInside,
contents: Contents<Node>,
},
OutOfFlowFloatBox {
style: Arc<ComputedValues>,
display_inside: DisplayInside,
contents: Contents<Node>,
},
}
/// 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 rayons `into_par_iter`.
enum IntermediateBlockContainer<Node> {
InlineFormattingContext(InlineFormattingContext),
Deferred { contents: NonReplacedContents<Node> },
}
/// 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<'dom, 'style, Node> {
context: &'style SharedStyleContext<'style>,
block_container_style: &'style 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<Node>, BoxSlot<'dom>)>,
/// 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<'dom, 'style>(
context: &SharedStyleContext<'style>,
block_container_style: &Arc<ComputedValues>,
contents: NonReplacedContents<impl NodeExt<'dom>>,
) -> (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);
}
// FIXME
// builder.end_ongoing_inline_formatting_context();
}
let mut contains_floats = builder.contains_floats;
let container = BlockContainer::BlockLevelBoxes(
builder
.block_level_boxes
.into_par_iter()
.mapfold_reduce_into(
&mut contains_floats,
|contains_floats, (intermediate, box_slot): (IntermediateBlockLevelBox<_>, BoxSlot<'_>)| {
let (block_level_box, box_contains_floats) = intermediate.finish(context);
*contains_floats |= box_contains_floats;
// FIXME
// box_slot.set(LayoutBox::BlockLevel(block_level_box.clone()));
block_level_box
},
|left, right| *left |= right,
)
.collect(),
);
(container, contains_floats)
}
}
impl<'dom, Node> TraversalHandler<Node> for BlockContainerBuilder<'dom, '_, Node>
where
Node: NodeExt<'dom>,
{
fn handle_element(
&mut self,
style: &Arc<ComputedValues>,
display: DisplayGeneratingBox,
contents: Contents<Node>,
box_slot: BoxSlot,
) {
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 => {
// FIXME
// 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)
// }
},
DisplayOutside::None => panic!(":("),
},
}
}
fn handle_text(&mut self, input: String, 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<'dom, Node> BlockContainerBuilder<'dom, '_, Node>
where
Node: NodeExt<'dom>,
{
/// 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<Node>,
) -> 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!()
},
DisplayInside::None | DisplayInside::Contents => panic!(":("),
},
};
self.current_inline_level_boxes().push(box_.clone());
box_
}
// FIXME
// 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<Node>,
box_slot: BoxSlot<'dom>,
) {
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(
unimplemented!(),
&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<Node>,
box_slot: BoxSlot<'dom>,
) {
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;
// FIXME
// 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<'dom, Node> IntermediateBlockLevelBox<Node>
where
Node: NodeExt<'dom>,
{
fn finish<'style>(
self,
context: &SharedStyleContext<'style>,
) -> (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<'dom, Node> IntermediateBlockContainer<Node>
where
Node: NodeExt<'dom>,
{
fn finish<'style>(
self,
context: &SharedStyleContext<'style>,
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(crate) 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
}
}

View file

@ -0,0 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::IndependentFormattingContext;
use servo_arc::Arc;
use style::properties::ComputedValues;
#[derive(Debug)]
pub(crate) struct FloatBox {
pub style: Arc<ComputedValues>,
pub contents: IndependentFormattingContext,
}
/// Data kept during layout about the floats in a given block formatting context.
pub(crate) struct FloatContext {
// TODO
}
impl FloatContext {
pub fn new() -> Self {
FloatContext {}
}
}

View file

@ -0,0 +1,291 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::flow::float::FloatBox;
use crate::flow::FlowChildren;
use crate::fragments::{AnonymousFragment, BoxFragment, CollapsedBlockMargins, Fragment};
use crate::geom::flow_relative::{Rect, Sides, Vec2};
use crate::positioned::{AbsolutelyPositionedBox, AbsolutelyPositionedFragment};
use crate::replaced::ReplacedContent;
use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayOutside};
use crate::{relative_adjustement, take, ContainingBlock};
use servo_arc::Arc;
use style::properties::ComputedValues;
use style::values::computed::Length;
use style::Zero;
#[derive(Debug, Default)]
pub(crate) struct InlineFormattingContext {
pub(super) inline_level_boxes: Vec<Arc<InlineLevelBox>>,
}
#[derive(Debug)]
pub(crate) enum InlineLevelBox {
InlineBox(InlineBox),
TextRun(TextRun),
OutOfFlowAbsolutelyPositionedBox(AbsolutelyPositionedBox),
OutOfFlowFloatBox(FloatBox),
Atomic {
style: Arc<ComputedValues>,
// FIXME: this should be IndependentFormattingContext:
contents: ReplacedContent,
},
}
#[derive(Debug)]
pub(crate) 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(crate) 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 Display::from(box_.style.get_box().original_display) {
Display::GeneratingBox(DisplayGeneratingBox::OutsideInside {
outside,
inside: _,
}) => Vec2 {
inline: match outside {
DisplayOutside::Inline => ifc.inline_position,
DisplayOutside::Block => Length::zero(),
DisplayOutside::None => unreachable!(":("),
},
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();
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) {
// TODO
}
}

View file

@ -0,0 +1,450 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Flow layout, also known as block-and-inline layout.
use crate::flow::float::{FloatBox, FloatContext};
use crate::flow::inline::InlineFormattingContext;
use crate::fragments::{
AnonymousFragment, BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment,
};
use crate::geom::flow_relative::{Rect, Vec2};
use crate::positioned::{
adjust_static_positions, AbsolutelyPositionedBox, AbsolutelyPositionedFragment,
};
use crate::style_ext::{ComputedValuesExt, Position};
use crate::{relative_adjustement, ContainingBlock, IndependentFormattingContext};
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
use rayon_croissant::ParallelIteratorExt;
use servo_arc::Arc;
use style::properties::ComputedValues;
use style::values::computed::{Length, LengthOrAuto};
use style::Zero;
mod construct;
mod float;
pub mod inline;
mod root;
#[derive(Debug)]
pub(crate) struct BlockFormattingContext {
pub contents: BlockContainer,
pub contains_floats: bool,
}
#[derive(Debug)]
pub(crate) enum BlockContainer {
BlockLevelBoxes(Vec<Arc<BlockLevelBox>>),
InlineFormattingContext(InlineFormattingContext),
}
#[derive(Debug)]
pub(crate) 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();
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::LengthPercentage(is) = inline_size {
let inline_margins = cbis - is - pb.inline_sum();
match (
&mut computed_margin.inline_start,
&mut computed_margin.inline_end,
) {
(s @ &mut LengthOrAuto::Auto, e @ &mut LengthOrAuto::Auto) => {
*s = LengthOrAuto::LengthPercentage(inline_margins / 2.);
*e = LengthOrAuto::LengthPercentage(inline_margins / 2.);
},
(s @ &mut LengthOrAuto::Auto, _) => {
*s = LengthOrAuto::LengthPercentage(inline_margins);
},
(_, e @ &mut LengthOrAuto::Auto) => {
*e = LengthOrAuto::LengthPercentage(inline_margins);
},
(_, e @ _) => {
// Either the inline-end margin is auto,
// or were over-constrained and we do as if it were.
*e = LengthOrAuto::LengthPercentage(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 = box_size
.block
.maybe_percentage_relative_to(containing_block.block_size.non_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.get_box().position == Position::Relative {
&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.get_box().position == Position::Relative {
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,
}
}

View file

@ -0,0 +1,129 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use super::*;
// FIXME
// impl crate::dom::Document {
// pub(crate) fn layout(
// &self,
// viewport: crate::geom::Size<crate::geom::CssPx>,
// ) -> Vec<Fragment> {
// BoxTreeRoot::construct(self).layout(viewport)
// }
// }
struct BoxTreeRoot(BlockFormattingContext);
// FIXME
// 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::geom::Size<crate::geom::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 documents 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 fragments = self.0.layout(
// &initial_containing_block,
// &mut absolutely_positioned_fragments,
// dummy_tree_rank,
// &mut PlacementState::root(),
// );
// let initial_containing_block = DefiniteContainingBlock {
// size: initial_containing_block_size,
// mode: initial_containing_block.mode,
// };
// fragments.par_extend(
// absolutely_positioned_fragments
// .par_iter()
// .map(|a| a.layout(&initial_containing_block)),
// );
// fragments
// }
// }

View file

@ -1,99 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! The `Fragment` type, which represents the leaves of the layout tree.
use crate::context::LayoutContext;
use crate::ServoArc;
use app_units::Au;
use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutNode};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use style::dom::OpaqueNode;
use style::logical_geometry::{LogicalMargin, LogicalRect};
use style::properties::ComputedValues;
use style::selector_parser::RestyleDamage;
use style::servo::restyle_damage::ServoRestyleDamage;
#[derive(Clone)]
pub struct Fragment {
pub node: OpaqueNode,
pub style: ServoArc<ComputedValues>,
pub border_box: LogicalRect<Au>,
pub border_padding: LogicalMargin<Au>,
pub margin: LogicalMargin<Au>,
pub specific: SpecificFragmentInfo,
pub restyle_damage: RestyleDamage,
pub pseudo: PseudoElementType,
}
impl Serialize for Fragment {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut serializer = serializer.serialize_struct("fragment", 3)?;
serializer.serialize_field("border_box", &self.border_box)?;
serializer.serialize_field("margin", &self.margin)?;
serializer.end()
}
}
#[derive(Clone)]
pub enum SpecificFragmentInfo {
Generic,
}
impl SpecificFragmentInfo {
fn restyle_damage(&self) -> RestyleDamage {
RestyleDamage::empty()
}
}
impl Fragment {
/// Constructs a new `Fragment` instance.
pub fn new<N: ThreadSafeLayoutNode>(
node: &N,
specific: SpecificFragmentInfo,
ctx: &LayoutContext,
) -> Fragment {
let shared_context = ctx.shared_context();
let style = node.style(shared_context);
let writing_mode = style.writing_mode;
let mut restyle_damage = RestyleDamage::rebuild_and_reflow();
restyle_damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW);
Fragment {
node: node.opaque(),
style: style,
restyle_damage: restyle_damage,
border_box: LogicalRect::zero(writing_mode),
border_padding: LogicalMargin::zero(writing_mode),
margin: LogicalMargin::zero(writing_mode),
specific: specific,
pseudo: node.get_pseudo_element_type(),
}
}
pub fn restyle_damage(&self) -> RestyleDamage {
self.restyle_damage | self.specific.restyle_damage()
}
pub fn contains_node(&self, node_address: OpaqueNode) -> bool {
node_address == self.node
}
/// Returns the sum of the inline-sizes of all the borders of this fragment. Note that this
/// can be expensive to compute, so if possible use the `border_padding` field instead.
#[inline]
pub fn border_width(&self) -> LogicalMargin<Au> {
self.style().logical_border_width()
}
#[inline(always)]
pub fn style(&self) -> &ComputedValues {
&*self.style
}
pub fn is_primary_fragment(&self) -> bool {
true
}
}

View file

@ -0,0 +1,125 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::geom::flow_relative::{Rect, Sides};
use crate::style_ext::{Direction, WritingMode};
// use crate::text::ShapedSegment;
use servo_arc::Arc;
use style::properties::ComputedValues;
use style::values::computed::Length;
use style::Zero;
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 blocks 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
}
}

View file

@ -0,0 +1,334 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::style_ext::{Direction, WritingMode};
use std::ops::{Add, AddAssign, Sub};
use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
use style::Zero;
use style_traits::CSSPixel;
pub type Point<U> = euclid::Point2D<f32, U>;
pub type Size<U> = euclid::Size2D<f32, U>;
pub type Rect<U> = euclid::Rect<f32, U>;
pub(crate) mod physical {
#[derive(Clone, Debug)]
pub(crate) struct Vec2<T> {
pub x: T,
pub y: T,
}
#[derive(Clone, Debug)]
pub(crate) struct Rect<T> {
pub top_left: Vec2<T>,
pub size: Vec2<T>,
}
#[derive(Clone, Debug)]
pub(crate) struct Sides<T> {
pub top: T,
pub left: T,
pub bottom: T,
pub right: T,
}
}
pub(crate) mod flow_relative {
#[derive(Clone, Debug)]
pub(crate) struct Vec2<T> {
pub inline: T,
pub block: T,
}
#[derive(Clone, Debug)]
pub(crate) struct Rect<T> {
pub start_corner: Vec2<T>,
pub size: Vec2<T>,
}
#[derive(Clone, Debug)]
pub(crate) struct Sides<T> {
pub inline_start: T,
pub inline_end: T,
pub block_start: T,
pub block_end: T,
}
}
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 Point<CSSPixel> {
fn from(v: physical::Vec2<Length>) -> Self {
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 => (&self.right, &self.left),
VerticalLr => (&self.left, &self.right),
};
let (is, ie) = match mode {
(HorizontalTb, Ltr) => (&self.left, &self.right),
(HorizontalTb, Rtl) => (&self.right, &self.left),
(VerticalRl, Ltr) | (VerticalLr, Ltr) => (&self.top, &self.bottom),
(VerticalRl, Rtl) | (VerticalLr, Rtl) => (&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<LengthPercentage> {
pub fn percentages_relative_to(&self, basis: Length) -> flow_relative::Sides<Length> {
self.map(|s| s.percentage_relative_to(basis))
}
}
impl flow_relative::Sides<LengthPercentageOrAuto> {
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 Rect<CSSPixel> {
fn from(r: physical::Rect<Length>) -> Self {
Rect {
origin: Point::new(r.top_left.x.px(), r.top_left.y.px()),
size: Size::new(r.size.x.px(), r.size.y.px()),
}
}
}

View file

@ -2,17 +2,159 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![allow(dead_code)]
#![allow(unreachable_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]
#![deny(unsafe_code)]
#[macro_use]
extern crate serde;
use style::properties::ComputedValues;
use style::values::computed::{Length, LengthOrAuto};
use style::Zero;
pub mod context;
pub mod data;
mod fragment;
pub mod dom_traversal;
pub mod element_data;
pub mod flow;
pub mod fragments;
pub mod geom;
pub mod opaque_node;
pub mod positioned;
pub mod query;
pub mod replaced;
pub mod style_ext;
pub mod traversal;
pub mod wrapper;
// For unit tests:
pub use crate::fragment::Fragment;
use crate::dom_traversal::{Contents, NodeExt};
use crate::flow::{BlockFormattingContext, FlowChildren};
use crate::geom::flow_relative::Vec2;
use crate::positioned::AbsolutelyPositionedFragment;
use crate::replaced::ReplacedContent;
use crate::style_ext::{ComputedValuesExt, Direction, Position, WritingMode};
use servo_arc::Arc;
use std::convert::TryInto;
use style::context::SharedStyleContext;
use style::values::specified::box_::DisplayInside;
use servo_arc::Arc as ServoArc;
/// https://drafts.csswg.org/css-display/#independent-formatting-context
#[derive(Debug)]
enum IndependentFormattingContext {
Flow(BlockFormattingContext),
// Not called FC in specs, but behaves close enough
Replaced(ReplacedContent),
// Other layout modes go here
}
enum NonReplacedIFC<'a> {
Flow(&'a BlockFormattingContext),
}
impl IndependentFormattingContext {
fn construct<'dom, 'style>(
context: &SharedStyleContext<'style>,
style: &Arc<ComputedValues>,
display_inside: DisplayInside,
contents: Contents<impl NodeExt<'dom>>,
) -> Self {
match contents.try_into() {
Ok(non_replaced) => match display_inside {
DisplayInside::Flow | DisplayInside::FlowRoot => {
IndependentFormattingContext::Flow(BlockFormattingContext::construct(
context,
style,
non_replaced,
))
},
DisplayInside::None | DisplayInside::Contents => panic!(":("),
},
Err(replaced) => IndependentFormattingContext::Replaced(replaced),
}
}
fn as_replaced(&self) -> Result<&ReplacedContent, NonReplacedIFC> {
match self {
IndependentFormattingContext::Replaced(r) => Ok(r),
IndependentFormattingContext::Flow(f) => Err(NonReplacedIFC::Flow(f)),
}
}
fn layout<'a>(
&'a self,
containing_block: &ContainingBlock,
tree_rank: usize,
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
) -> FlowChildren {
match self.as_replaced() {
Ok(replaced) => match *replaced {},
Err(ifc) => ifc.layout(containing_block, tree_rank, absolutely_positioned_fragments),
}
}
}
impl<'a> NonReplacedIFC<'a> {
fn layout(
&self,
containing_block: &ContainingBlock,
tree_rank: usize,
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
) -> FlowChildren {
match self {
NonReplacedIFC::Flow(bfc) => {
bfc.layout(containing_block, tree_rank, absolutely_positioned_fragments)
},
}
}
}
struct ContainingBlock {
inline_size: Length,
block_size: LengthOrAuto,
mode: (WritingMode, Direction),
}
struct DefiniteContainingBlock {
size: Vec2<Length>,
mode: (WritingMode, Direction),
}
/// https://drafts.csswg.org/css2/visuren.html#relative-positioning
fn relative_adjustement(
style: &ComputedValues,
inline_size: Length,
block_size: LengthOrAuto,
) -> Vec2<Length> {
if style.get_box().position != Position::Relative {
return Vec2::zero();
}
fn adjust(start: LengthOrAuto, end: LengthOrAuto) -> Length {
match (start, end) {
(LengthOrAuto::Auto, LengthOrAuto::Auto) => Length::zero(),
(LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(end)) => -end,
(LengthOrAuto::LengthPercentage(start), _) => start,
}
}
let block_size = block_size.auto_is(Length::zero);
let box_offsets = style.box_offsets().map_inline_and_block_axes(
|v| v.percentage_relative_to(inline_size),
|v| v.percentage_relative_to(block_size),
);
Vec2 {
inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
block: adjust(box_offsets.block_start, box_offsets.block_end),
}
}
// FIXME: use std::mem::take when its stable.
// https://github.com/rust-lang/rust/issues/61129
fn take<T>(x: &mut T) -> T
where
T: Default,
{
std::mem::replace(x, Default::default())
}

View file

@ -0,0 +1,341 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::fragments::{AnonymousFragment, BoxFragment, CollapsedBlockMargins, Fragment};
use crate::geom::flow_relative::{Rect, Sides, Vec2};
use crate::style_ext::{ComputedValuesExt, Direction, WritingMode};
use crate::{ContainingBlock, DefiniteContainingBlock, IndependentFormattingContext};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use servo_arc::Arc;
use style::properties::ComputedValues;
use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
use style::Zero;
#[derive(Debug)]
pub(crate) struct AbsolutelyPositionedBox {
pub style: Arc<ComputedValues>,
pub contents: IndependentFormattingContext,
}
#[derive(Debug)]
pub(crate) 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(crate) tree_rank: usize,
pub(crate) inline_start: AbsoluteBoxOffsets<LengthPercentage>,
inline_size: LengthPercentageOrAuto,
pub(crate) block_start: AbsoluteBoxOffsets<LengthPercentage>,
block_size: LengthPercentageOrAuto,
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum AbsoluteBoxOffsets<NonStatic> {
StaticStart { start: Length },
Start { start: NonStatic },
End { end: NonStatic },
Both { start: NonStatic, end: NonStatic },
}
impl AbsolutelyPositionedBox {
pub(crate) 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: LengthPercentageOrAuto,
end: LengthPercentageOrAuto,
) -> AbsoluteBoxOffsets<LengthPercentage> {
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(crate) 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 rects 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(crate) 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();
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<LengthPercentage>,
size: LengthPercentageOrAuto,
) -> (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::LengthPercentage(size) = size {
let margins = containing_size - start - end - padding_border_sum - size;
match (computed_margin_start, computed_margin_end) {
(LengthOrAuto::Auto, LengthOrAuto::Auto) => {
let (s, e) = solve_margins(margins);
margin_start = s;
margin_end = e;
},
(LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(end)) => {
margin_start = margins - end;
},
(LengthOrAuto::LengthPercentage(start), LengthOrAuto::Auto) => {
margin_end = margins - start;
},
(
LengthOrAuto::LengthPercentage(_),
LengthOrAuto::LengthPercentage(_),
) => {},
}
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::LengthPercentage(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(crate) 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;
}
}
}

View file

@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::dom_traversal::NodeExt;
use style::context::SharedStyleContext;
#[derive(Debug)]
pub(super) enum ReplacedContent {
// Not implemented yet
}
impl ReplacedContent {
pub fn for_element<'dom, Node>(element: Node, _context: &SharedStyleContext) -> Option<Self>
where
Node: NodeExt<'dom>,
{
// FIXME: implement <img> etc.
None
}
}

View file

@ -0,0 +1,129 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::geom::{flow_relative, physical};
use style::properties::ComputedValues;
use style::values::computed::{
Display as PackedDisplay, Length, LengthPercentage, LengthPercentageOrAuto, Size,
};
pub use style::computed_values::direction::T as Direction;
pub use style::computed_values::position::T as Position;
pub use style::computed_values::writing_mode::T as WritingMode;
pub use style::values::specified::box_::{DisplayInside, DisplayOutside};
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) enum Display {
None,
Contents,
GeneratingBox(DisplayGeneratingBox),
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) enum DisplayGeneratingBox {
OutsideInside {
outside: DisplayOutside,
inside: DisplayInside,
// list_item: bool,
},
// Layout-internal display types go here:
// https://drafts.csswg.org/css-display-3/#layout-specific-display
}
pub(crate) trait ComputedValuesExt {
fn writing_mode(&self) -> (WritingMode, Direction);
fn box_offsets(&self) -> flow_relative::Sides<LengthPercentageOrAuto>;
fn box_size(&self) -> flow_relative::Vec2<LengthPercentageOrAuto>;
fn padding(&self) -> flow_relative::Sides<LengthPercentage>;
fn border_width(&self) -> flow_relative::Sides<Length>;
fn margin(&self) -> flow_relative::Sides<LengthPercentageOrAuto>;
}
impl ComputedValuesExt for ComputedValues {
fn writing_mode(&self) -> (WritingMode, Direction) {
let inherited_box = self.get_inherited_box();
let writing_mode = inherited_box.writing_mode;
let direction = inherited_box.direction;
(writing_mode, direction)
}
#[inline]
fn box_offsets(&self) -> flow_relative::Sides<LengthPercentageOrAuto> {
let position = self.get_position();
physical::Sides {
top: position.top,
left: position.left,
bottom: position.bottom,
right: position.right,
}
.to_flow_relative(self.writing_mode())
}
#[inline]
fn box_size(&self) -> flow_relative::Vec2<LengthPercentageOrAuto> {
let position = self.get_position();
physical::Vec2 {
x: size_to_length(position.width),
y: size_to_length(position.height),
}
.size_to_flow_relative(self.writing_mode())
}
#[inline]
fn padding(&self) -> flow_relative::Sides<LengthPercentage> {
let padding = self.get_padding();
physical::Sides {
top: padding.padding_top.0,
left: padding.padding_left.0,
bottom: padding.padding_bottom.0,
right: padding.padding_right.0,
}
.to_flow_relative(self.writing_mode())
}
fn border_width(&self) -> flow_relative::Sides<Length> {
let border = self.get_border();
physical::Sides {
top: border.border_top_width.0,
left: border.border_left_width.0,
bottom: border.border_bottom_width.0,
right: border.border_right_width.0,
}
.to_flow_relative(self.writing_mode())
}
fn margin(&self) -> flow_relative::Sides<LengthPercentageOrAuto> {
let margin = self.get_margin();
physical::Sides {
top: margin.margin_top,
left: margin.margin_left,
bottom: margin.margin_bottom,
right: margin.margin_right,
}
.to_flow_relative(self.writing_mode())
}
}
impl From<PackedDisplay> for Display {
fn from(packed_display: PackedDisplay) -> Self {
if packed_display == PackedDisplay::None {
return Self::None;
}
if packed_display == PackedDisplay::Contents {
return Self::Contents;
}
Self::GeneratingBox(DisplayGeneratingBox::OutsideInside {
outside: packed_display.outside(),
inside: packed_display.inside(),
// list_item: packed_display.is_list_item(),
})
}
}
fn size_to_length(size: Size) -> LengthPercentageOrAuto {
match size {
Size::LengthPercentage(length) => LengthPercentageOrAuto::LengthPercentage(length.0),
Size::Auto => LengthPercentageOrAuto::Auto,
}
}

View file

@ -11,13 +11,13 @@ use style::dom::{NodeInfo, TElement, TNode};
use style::traversal::PerLevelTraversalData;
use style::traversal::{recalc_style_at, DomTraversal};
pub struct RecalcStyleAndConstructFlows<'a> {
pub struct RecalcStyle<'a> {
context: LayoutContext<'a>,
}
impl<'a> RecalcStyleAndConstructFlows<'a> {
impl<'a> RecalcStyle<'a> {
pub fn new(context: LayoutContext<'a>) -> Self {
RecalcStyleAndConstructFlows { context: context }
RecalcStyle { context: context }
}
pub fn destroy(self) -> LayoutContext<'a> {
@ -26,7 +26,7 @@ impl<'a> RecalcStyleAndConstructFlows<'a> {
}
#[allow(unsafe_code)]
impl<'a, E> DomTraversal<E> for RecalcStyleAndConstructFlows<'a>
impl<'a, E> DomTraversal<E> for RecalcStyle<'a>
where
E: TElement,
E::ConcreteNode: LayoutNode,

View file

@ -29,11 +29,9 @@ use embedder_traits::resources::{self, Resource};
use euclid::{default::Size2D as UntypedSize2D, Point2D, Rect, Scale, Size2D};
use fnv::FnvHashMap;
use fxhash::FxHashMap;
use gfx::font;
use gfx::font_cache_thread::FontCacheThread;
use gfx::font_context;
use gfx_traits::{node_id_from_scroll_id, Epoch};
use histogram::Histogram;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use layout::context::LayoutContext;
@ -46,7 +44,7 @@ use layout::query::{
process_offset_parent_query, process_resolved_style_request, process_style_query,
process_text_index_request,
};
use layout::traversal::RecalcStyleAndConstructFlows;
use layout::traversal::RecalcStyle;
use layout_traits::LayoutThreadFactory;
use libc::c_void;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
@ -86,10 +84,10 @@ use std::time::Duration;
use style::animation::Animation;
use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters};
use style::context::{SharedStyleContext, ThreadLocalStyleContextCreationInfo};
use style::dom::{ShowSubtree, TDocument, TElement, TNode};
use style::dom::{TDocument, TElement, TNode};
use style::driver;
use style::error_reporting::RustLogReporter;
use style::global_style_data::{GLOBAL_STYLE_DATA, STYLE_THREAD_POOL};
use style::global_style_data::GLOBAL_STYLE_DATA;
use style::invalidation::element::restyle_hints::RestyleHint;
use style::media_queries::{Device, MediaList, MediaType};
use style::properties::PropertyId;
@ -154,9 +152,6 @@ pub struct LayoutThread {
/// Is this the first reflow in this LayoutThread?
first_reflow: Cell<bool>,
/// Flag to indicate whether to use parallel operations
parallel_flag: bool,
/// Starts at zero, and increased by one every time a layout completes.
/// This can be used to easily check for invalid stale data.
generation: Cell<u32>,
@ -203,9 +198,6 @@ pub struct LayoutThread {
/// Paint time metrics.
paint_time_metrics: PaintTimeMetrics,
/// The time a layout query has waited before serviced by layout thread.
layout_query_waiting_time: Histogram,
/// Flag that indicates if LayoutThread is busy handling a request.
busy: Arc<AtomicBool>,
@ -496,7 +488,6 @@ impl LayoutThread {
first_reflow: Cell::new(true),
font_cache_receiver: font_cache_receiver,
font_cache_sender: ipc_font_cache_sender,
parallel_flag: true,
generation: Cell::new(0),
new_animations_sender: new_animations_sender,
_new_animations_receiver: new_animations_receiver,
@ -529,7 +520,6 @@ impl LayoutThread {
Timer::new()
},
paint_time_metrics: paint_time_metrics,
layout_query_waiting_time: Histogram::new(),
busy,
load_webfonts_synchronously,
initial_window_size,
@ -575,6 +565,7 @@ impl LayoutThread {
traversal_flags: TraversalFlags::empty(),
snapshot_map: snapshot_map,
},
font_cache_thread: Mutex::new(self.font_cache_thread.clone()),
}
}
@ -853,21 +844,7 @@ impl LayoutThread {
}
}
/// Shuts down the layout thread now. If there are any DOM nodes left, layout will now (safely)
/// crash.
fn exit_now(&mut self) {
// Drop the root flow explicitly to avoid holding style data, such as
// rule nodes. The `Stylist` checks when it is dropped that all rule
// nodes have been GCed, so we want drop anyone who holds them first.
let waiting_time_min = self.layout_query_waiting_time.minimum().unwrap_or(0);
let waiting_time_max = self.layout_query_waiting_time.maximum().unwrap_or(0);
let waiting_time_mean = self.layout_query_waiting_time.mean().unwrap_or(0);
let waiting_time_stddev = self.layout_query_waiting_time.stddev().unwrap_or(0);
debug!(
"layout: query waiting time: min: {}, max: {}, mean: {}, standard_deviation: {}",
waiting_time_min, waiting_time_max, waiting_time_mean, waiting_time_stddev
);
self.background_hang_monitor.unregister();
}
@ -909,23 +886,8 @@ impl LayoutThread {
let document = unsafe { ServoLayoutNode::new(&data.document) };
let document = document.as_document().unwrap();
// Parallelize if there's more than 750 objects based on rzambre's suggestion
// https://github.com/servo/servo/issues/10110
self.parallel_flag = data.dom_count > 750;
debug!("layout: received layout request for: {}", self.url);
debug!("Number of objects in DOM: {}", data.dom_count);
debug!("layout: parallel? {}", self.parallel_flag);
let mut rw_data = possibly_locked_rw_data.lock();
// Record the time that layout query has been waited.
let now = time::precise_time_ns();
if let ReflowGoal::LayoutQuery(_, timestamp) = data.reflow_goal {
self.layout_query_waiting_time
.increment(now - timestamp)
.expect("layout: wrong layout query timestamp");
};
let element = match document.root_element() {
None => {
// Since we cannot compute anything, give spec-required placeholders.
@ -973,12 +935,6 @@ impl LayoutThread {
Some(x) => x,
};
debug!(
"layout: processing reflow request for: {:?} ({}) (query={:?})",
element, self.url, data.reflow_goal
);
trace!("{:?}", ShowSubtree(element.as_node()));
let initial_viewport = data.window_size.initial_viewport;
let device_pixel_ratio = data.window_size.device_pixel_ratio;
let old_viewport_size = self.viewport_size;
@ -1010,9 +966,6 @@ impl LayoutThread {
self.stylist
.viewport_constraints()
.map_or(current_screen_size, |constraints| {
debug!("Viewport constraints: {:?}", constraints);
// other rules are evaluated against the actual viewport
Size2D::new(
Au::from_f32_px(constraints.size.width),
Au::from_f32_px(constraints.size.height),
@ -1038,38 +991,29 @@ impl LayoutThread {
}
}
{
if self.first_reflow.get() {
debug!("First reflow, rebuilding user and UA rules");
for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets {
self.stylist
.append_stylesheet(stylesheet.clone(), &ua_or_user_guard);
self.handle_add_stylesheet(&stylesheet.0, &ua_or_user_guard);
}
if self.stylist.quirks_mode() != QuirksMode::NoQuirks {
self.stylist.append_stylesheet(
ua_stylesheets.quirks_mode_stylesheet.clone(),
&ua_or_user_guard,
);
self.handle_add_stylesheet(
&ua_stylesheets.quirks_mode_stylesheet.0,
&ua_or_user_guard,
);
}
if self.first_reflow.get() {
for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets {
self.stylist
.append_stylesheet(stylesheet.clone(), &ua_or_user_guard);
self.handle_add_stylesheet(&stylesheet.0, &ua_or_user_guard);
}
if data.stylesheets_changed {
debug!("Doc sheets changed, flushing author sheets too");
self.stylist
.force_stylesheet_origins_dirty(Origin::Author.into());
if self.stylist.quirks_mode() != QuirksMode::NoQuirks {
self.stylist.append_stylesheet(
ua_stylesheets.quirks_mode_stylesheet.clone(),
&ua_or_user_guard,
);
self.handle_add_stylesheet(
&ua_stylesheets.quirks_mode_stylesheet.0,
&ua_or_user_guard,
);
}
}
debug!(
"Shadow roots in document {:?}",
document.shadow_roots().len()
);
if data.stylesheets_changed {
self.stylist
.force_stylesheet_origins_dirty(Origin::Author.into());
}
// Flush shadow roots stylesheets if dirty.
document.flush_shadow_roots_stylesheets(
@ -1079,7 +1023,6 @@ impl LayoutThread {
);
let restyles = document.drain_pending_restyles();
debug!("Draining restyles: {}", restyles.len());
let mut map = SnapshotMap::new();
let elements_with_snapshot: Vec<_> = restyles
@ -1124,51 +1067,14 @@ impl LayoutThread {
// Create a layout context for use throughout the following passes.
let mut layout_context = self.build_layout_context(guards.clone(), &map);
let (thread_pool, num_threads) = if self.parallel_flag {
(
STYLE_THREAD_POOL.style_thread_pool.as_ref(),
STYLE_THREAD_POOL.num_threads,
)
} else {
(None, 1)
};
let traversal = RecalcStyleAndConstructFlows::new(layout_context);
let traversal = RecalcStyle::new(layout_context);
let token = {
let shared =
<RecalcStyleAndConstructFlows as DomTraversal<ServoLayoutElement>>::shared_context(
&traversal,
);
RecalcStyleAndConstructFlows::pre_traverse(element, shared)
let shared = DomTraversal::<ServoLayoutElement>::shared_context(&traversal);
RecalcStyle::pre_traverse(element, shared)
};
if token.should_traverse() {
// Recalculate CSS styles and rebuild flows and fragments.
profile(
profile_time::ProfilerCategory::LayoutStyleRecalc,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
// Perform CSS selector matching and flow construction.
driver::traverse_dom::<ServoLayoutElement, RecalcStyleAndConstructFlows>(
&traversal,
token,
thread_pool,
);
},
);
// TODO(pcwalton): Measure energy usage of text shaping, perhaps?
let text_shaping_time =
font::get_and_reset_text_shaping_performance_counter() / num_threads;
profile_time::send_profile_data(
profile_time::ProfilerCategory::LayoutTextShaping,
self.profiler_metadata(),
&self.time_profiler_chan,
0,
text_shaping_time as u64,
0,
0,
);
driver::traverse_dom(&traversal, token, None);
}
for element in elements_with_snapshot {

View file

@ -12,6 +12,7 @@ use crate::properties::longhands::float::computed_value::T as Float;
use crate::properties::longhands::overflow_x::computed_value::T as Overflow;
use crate::properties::longhands::position::computed_value::T as Position;
use crate::properties::{self, ComputedValues, StyleBuilder};
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
use crate::values::specified::box_::DisplayInside;
use app_units::Au;
@ -183,6 +184,7 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
where
E: TElement,
{
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
use crate::computed_values::list_style_position::T as ListStylePosition;
let mut blockify = false;
@ -205,6 +207,7 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
blockify_if!(self.style.floated());
blockify_if!(self.style.out_of_flow_positioned());
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
blockify_if!(
self.style.pseudo.map_or(false, |p| p.is_marker()) &&
self.style.get_parent_list().clone_list_style_position() ==

View file

@ -20,9 +20,9 @@ use crate::Zero;
use app_units::Au;
use ordered_float::NotNan;
use std::fmt::{self, Write};
use std::ops::{Add, Neg};
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub};
use style_traits::values::specified::AllowedNumericType;
use style_traits::{CssWriter, ToCss};
use style_traits::{CSSPixel, CssWriter, ToCss};
pub use super::image::Image;
pub use crate::values::specified::url::UrlOrNone;
@ -206,20 +206,26 @@ impl LengthPercentage {
Some(Percentage(self.clamping_mode.clamp(self.percentage.0)))
}
/// Resolves the percentage.
#[inline]
pub fn percentage_relative_to(&self, basis: Length) -> Length {
let length = self.unclamped_length().0 + basis.0 * self.percentage.0;
Length::new(self.clamping_mode.clamp(length))
}
/// Convert the computed value into used value.
#[inline]
pub fn maybe_to_used_value(&self, container_len: Option<Au>) -> Option<Au> {
self.maybe_to_pixel_length(container_len).map(Au::from)
pub fn maybe_to_used_value(&self, container_len: Option<Length>) -> Option<Au> {
self.maybe_percentage_relative_to(container_len)
.map(Au::from)
}
/// If there are special rules for computing percentages in a value (e.g.
/// the height property), they apply whenever a calc() expression contains
/// percentages.
pub fn maybe_to_pixel_length(&self, container_len: Option<Au>) -> Option<Length> {
pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> {
if self.has_percentage {
let length = self.unclamped_length().px() +
container_len?.scale_by(self.percentage.0).to_f32_px();
return Some(Length::new(self.clamping_mode.clamp(length)));
return Some(self.percentage_relative_to(container_len?));
}
Some(self.length())
}
@ -371,7 +377,7 @@ impl LengthPercentage {
/// Returns the used value as CSSPixelLength.
pub fn to_pixel_length(&self, containing_length: Au) -> Length {
self.maybe_to_pixel_length(Some(containing_length)).unwrap()
self.percentage_relative_to(containing_length.into())
}
/// Returns the clamped non-negative values.
@ -488,6 +494,30 @@ impl LengthPercentageOrAuto {
}
computed_length_percentage_or_auto!(LengthPercentage);
/// Resolves the percentage.
#[inline]
pub fn percentage_relative_to(&self, basis: Length) -> LengthOrAuto {
use values::generics::length::LengthPercentageOrAuto::*;
match self {
LengthPercentage(length_percentage) => {
LengthPercentage(length_percentage.percentage_relative_to(basis))
},
Auto => Auto,
}
}
/// Maybe resolves the percentage.
#[inline]
pub fn maybe_percentage_relative_to(&self, basis: Option<Length>) -> LengthOrAuto {
use values::generics::length::LengthPercentageOrAuto::*;
match self {
LengthPercentage(length_percentage) => length_percentage
.maybe_percentage_relative_to(basis)
.map_or(Auto, LengthPercentage),
Auto => Auto,
}
}
}
/// A wrapper of LengthPercentageOrAuto, whose value must be >= 0.
@ -555,7 +585,9 @@ impl NonNegativeLengthPercentage {
/// Convert the computed value into used value.
#[inline]
pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> {
let resolved = self.0.maybe_to_used_value(containing_length)?;
let resolved = self
.0
.maybe_to_used_value(containing_length.map(|v| v.into()))?;
Some(::std::cmp::max(resolved, Au(0)))
}
}
@ -644,6 +676,23 @@ impl CSSPixelLength {
pub fn clamp_to_non_negative(self) -> Self {
CSSPixelLength::new(self.0.max(0.))
}
/// Returns the minimum between `self` and `other`.
#[inline]
pub fn min(self, other: Self) -> Self {
CSSPixelLength::new(self.0.min(other.0))
}
/// Returns the maximum between `self` and `other`.
#[inline]
pub fn max(self, other: Self) -> Self {
CSSPixelLength::new(self.0.max(other.0))
}
/// Sets `self` to the maximum between `self` and `other`.
pub fn max_assign(&mut self, other: Self) {
*self = self.max(other);
}
}
impl Zero for CSSPixelLength {
@ -676,6 +725,31 @@ impl Add for CSSPixelLength {
}
}
impl AddAssign for CSSPixelLength {
#[inline]
fn add_assign(&mut self, other: Self) {
self.0 += other.0;
}
}
impl Div<CSSFloat> for CSSPixelLength {
type Output = Self;
#[inline]
fn div(self, other: CSSFloat) -> Self {
Self::new(self.px() / other)
}
}
impl Mul<CSSFloat> for CSSPixelLength {
type Output = Self;
#[inline]
fn mul(self, other: CSSFloat) -> Self {
Self::new(self.px() * other)
}
}
impl Neg for CSSPixelLength {
type Output = Self;
@ -685,6 +759,15 @@ impl Neg for CSSPixelLength {
}
}
impl Sub for CSSPixelLength {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self {
Self::new(self.px() - other.px())
}
}
impl From<CSSPixelLength> for Au {
#[inline]
fn from(len: CSSPixelLength) -> Self {
@ -699,6 +782,13 @@ impl From<Au> for CSSPixelLength {
}
}
impl From<CSSPixelLength> for euclid::Length<CSSFloat, CSSPixel> {
#[inline]
fn from(length: CSSPixelLength) -> Self {
Self::new(length.0)
}
}
/// An alias of computed `<length>` value.
pub type Length = CSSPixelLength;

View file

@ -69,6 +69,39 @@ impl<LengthPercentage> LengthPercentageOrAuto<LengthPercentage> {
}
}
impl<LengthPercentage> LengthPercentageOrAuto<LengthPercentage>
where
LengthPercentage: Clone,
{
/// Resolves `auto` values by calling `f`.
#[inline]
pub fn auto_is(&self, f: impl Fn() -> LengthPercentage) -> LengthPercentage {
match self {
LengthPercentageOrAuto::LengthPercentage(length) => length.clone(),
LengthPercentageOrAuto::Auto => f(),
}
}
/// Returns the non-`auto` value, if any.
#[inline]
pub fn non_auto(&self) -> Option<LengthPercentage> {
match self {
LengthPercentageOrAuto::LengthPercentage(length) => Some(length.clone()),
LengthPercentageOrAuto::Auto => None,
}
}
/// Maps the length of this value.
pub fn map(&self, f: impl FnOnce(LengthPercentage) -> LengthPercentage) -> Self {
match self {
LengthPercentageOrAuto::LengthPercentage(l) => {
LengthPercentageOrAuto::LengthPercentage(f(l.clone()))
},
LengthPercentageOrAuto::Auto => LengthPercentageOrAuto::Auto,
}
}
}
impl<LengthPercentage: Zero> Zero for LengthPercentageOrAuto<LengthPercentage> {
fn zero() -> Self {
LengthPercentageOrAuto::LengthPercentage(Zero::zero())

View file

@ -44,7 +44,9 @@ pub enum DisplayOutside {
None = 0,
Inline,
Block,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
TableCaption,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
InternalTable,
#[cfg(feature = "gecko")]
InternalRuby,
@ -57,21 +59,32 @@ pub enum DisplayOutside {
#[repr(u8)]
pub enum DisplayInside {
None = 0,
#[cfg(feature = "gecko")]
#[cfg(any(feature = "servo-layout-2020", feature = "gecko"))]
Contents,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
Block,
FlowRoot,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
Inline,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
Flex,
#[cfg(feature = "gecko")]
Grid,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
Table,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
TableRowGroup,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
TableColumn,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
TableColumnGroup,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
TableHeaderGroup,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
TableFooterGroup,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
TableRow,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
TableCell,
#[cfg(feature = "gecko")]
Ruby,
@ -138,21 +151,32 @@ impl Display {
/// https://drafts.csswg.org/css-display/#the-display-properties
pub const None: Self = Self::new(DisplayOutside::None, DisplayInside::None);
#[cfg(feature = "gecko")]
#[cfg(any(feature = "servo-layout-2020", feature = "gecko"))]
pub const Contents: Self = Self::new(DisplayOutside::None, DisplayInside::Contents);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const Inline: Self = Self::new(DisplayOutside::Inline, DisplayInside::Inline);
#[cfg(any(feature = "servo-layout-2020"))]
pub const Inline: Self = Self::new(DisplayOutside::Inline, DisplayInside::Flow);
pub const InlineBlock: Self = Self::new(DisplayOutside::Inline, DisplayInside::FlowRoot);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const Block: Self = Self::new(DisplayOutside::Block, DisplayInside::Block);
#[cfg(any(feature = "servo-layout-2020"))]
pub const Block: Self = Self::new(DisplayOutside::Block, DisplayInside::Flow);
#[cfg(feature = "gecko")]
pub const FlowRoot: Self = Self::new(DisplayOutside::Block, DisplayInside::FlowRoot);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const Flex: Self = Self::new(DisplayOutside::Block, DisplayInside::Flex);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const InlineFlex: Self = Self::new(DisplayOutside::Inline, DisplayInside::Flex);
#[cfg(feature = "gecko")]
pub const Grid: Self = Self::new(DisplayOutside::Block, DisplayInside::Grid);
#[cfg(feature = "gecko")]
pub const InlineGrid: Self = Self::new(DisplayOutside::Inline, DisplayInside::Grid);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const Table: Self = Self::new(DisplayOutside::Block, DisplayInside::Table);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const InlineTable: Self = Self::new(DisplayOutside::Inline, DisplayInside::Table);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const TableCaption: Self = Self::new(DisplayOutside::TableCaption, DisplayInside::Block);
#[cfg(feature = "gecko")]
pub const Ruby: Self = Self::new(DisplayOutside::Inline, DisplayInside::Ruby);
@ -160,25 +184,39 @@ impl Display {
pub const WebkitBox: Self = Self::new(DisplayOutside::Block, DisplayInside::WebkitBox);
#[cfg(feature = "gecko")]
pub const WebkitInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::WebkitBox);
/// Internal table boxes.
// Internal table boxes.
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const TableRowGroup: Self =
Self::new(DisplayOutside::InternalTable, DisplayInside::TableRowGroup);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const TableHeaderGroup: Self = Self::new(
DisplayOutside::InternalTable,
DisplayInside::TableHeaderGroup,
);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const TableFooterGroup: Self = Self::new(
DisplayOutside::InternalTable,
DisplayInside::TableFooterGroup,
);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const TableColumn: Self =
Self::new(DisplayOutside::InternalTable, DisplayInside::TableColumn);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const TableColumnGroup: Self = Self::new(
DisplayOutside::InternalTable,
DisplayInside::TableColumnGroup,
);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const TableRow: Self = Self::new(DisplayOutside::InternalTable, DisplayInside::TableRow);
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
pub const TableCell: Self = Self::new(DisplayOutside::InternalTable, DisplayInside::TableCell);
/// Internal ruby boxes.
@ -235,6 +273,7 @@ impl Display {
#[inline]
fn from3(outside: DisplayOutside, inside: DisplayInside, list_item: bool) -> Self {
let inside = match inside {
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
DisplayInside::Flow => match outside {
DisplayOutside::Inline => DisplayInside::Inline,
_ => DisplayInside::Block,
@ -307,6 +346,7 @@ impl Display {
pub fn is_atomic_inline_level(&self) -> bool {
match *self {
Display::InlineBlock => true,
#[cfg(any(feature = "servo-layout-2013"))]
Display::InlineFlex | Display::InlineTable => true,
_ => false,
}
@ -318,6 +358,7 @@ impl Display {
/// This is used to implement various style fixups.
pub fn is_item_container(&self) -> bool {
match self.inside() {
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
DisplayInside::Flex => true,
#[cfg(feature = "gecko")]
DisplayInside::Grid => true,
@ -352,12 +393,16 @@ impl Display {
match self.outside() {
DisplayOutside::Inline => {
let inside = match self.inside() {
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
DisplayInside::Inline | DisplayInside::FlowRoot => DisplayInside::Block,
#[cfg(feature = "servo-layout-2020")]
DisplayInside::FlowRoot => DisplayInside::Flow,
inside => inside,
};
Display::from3(DisplayOutside::Block, inside, self.is_list_item())
},
DisplayOutside::Block | DisplayOutside::None => *self,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
_ => Display::Block,
}
}
@ -413,6 +458,7 @@ impl ToCss for Display {
);
let outside = self.outside();
let inside = match self.inside() {
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
DisplayInside::Block | DisplayInside::Inline => DisplayInside::Flow,
inside => inside,
};
@ -427,10 +473,12 @@ impl ToCss for Display {
Display::MozInlineGrid => dest.write_str("-moz-inline-grid"),
#[cfg(feature = "gecko")]
Display::MozInlineStack => dest.write_str("-moz-inline-stack"),
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
Display::TableCaption => dest.write_str("table-caption"),
_ => match (outside, inside) {
#[cfg(feature = "gecko")]
(DisplayOutside::Inline, DisplayInside::Grid) => dest.write_str("inline-grid"),
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
(DisplayOutside::Inline, DisplayInside::Flex) |
(DisplayOutside::Inline, DisplayInside::Table) => {
dest.write_str("inline-")?;
@ -465,9 +513,11 @@ fn parse_display_inside<'i, 't>(
) -> Result<DisplayInside, ParseError<'i>> {
Ok(try_match_ident_ignore_ascii_case! { input,
"flow" => DisplayInside::Flow,
#[cfg(feature = "gecko")]
#[cfg(any(feature = "servo-layout-2020", feature = "gecko"))]
"flow-root" => DisplayInside::FlowRoot,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"table" => DisplayInside::Table,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"flex" => DisplayInside::Flex,
#[cfg(feature = "gecko")]
"grid" => DisplayInside::Grid,
@ -569,18 +619,29 @@ impl Parse for Display {
#[cfg(feature = "gecko")]
"contents" => Display::Contents,
"inline-block" => Display::InlineBlock,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"inline-table" => Display::InlineTable,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"-webkit-flex" => Display::Flex,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"inline-flex" | "-webkit-inline-flex" => Display::InlineFlex,
#[cfg(feature = "gecko")]
"inline-grid" => Display::InlineGrid,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"table-caption" => Display::TableCaption,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"table-row-group" => Display::TableRowGroup,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"table-header-group" => Display::TableHeaderGroup,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"table-footer-group" => Display::TableFooterGroup,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"table-column" => Display::TableColumn,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"table-column-group" => Display::TableColumnGroup,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"table-row" => Display::TableRow,
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
"table-cell" => Display::TableCell,
#[cfg(feature = "gecko")]
"ruby-base" => Display::RubyBase,