From d0d4bb7c99e9cea754552a51748d260a1aa88cee Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Tue, 20 Aug 2019 11:44:09 +0200 Subject: [PATCH 1/5] Kill fragments --- Cargo.lock | 1 - components/layout_2020/Cargo.toml | 1 - components/layout_2020/fragment.rs | 99 ------------------------------ components/layout_2020/lib.rs | 6 -- 4 files changed, 107 deletions(-) delete mode 100644 components/layout_2020/fragment.rs diff --git a/Cargo.lock b/Cargo.lock index e34c348c01c..1d73ed16de3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2334,7 +2334,6 @@ dependencies = [ "script_layout_interface 0.0.1", "script_traits 0.0.1", "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", - "servo_arc 0.1.1", "servo_url 0.0.1", "style 0.0.1", "style_traits 0.0.1", diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml index 0e743765cd1..296d069c7e1 100644 --- a/components/layout_2020/Cargo.toml +++ b/components/layout_2020/Cargo.toml @@ -27,7 +27,6 @@ rayon = "1" script_layout_interface = {path = "../script_layout_interface"} script_traits = {path = "../script_traits"} serde = "1.0" -servo_arc = {path = "../servo_arc"} servo_url = {path = "../url"} style = {path = "../style", features = ["servo", "servo-layout-2020"]} style_traits = {path = "../style_traits"} diff --git a/components/layout_2020/fragment.rs b/components/layout_2020/fragment.rs deleted file mode 100644 index 71e23b7ffaa..00000000000 --- a/components/layout_2020/fragment.rs +++ /dev/null @@ -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, - pub border_box: LogicalRect, - pub border_padding: LogicalMargin, - pub margin: LogicalMargin, - pub specific: SpecificFragmentInfo, - pub restyle_damage: RestyleDamage, - pub pseudo: PseudoElementType, -} - -impl Serialize for Fragment { - fn serialize(&self, serializer: S) -> Result { - 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( - 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 { - self.style().logical_border_width() - } - - #[inline(always)] - pub fn style(&self) -> &ComputedValues { - &*self.style - } - - pub fn is_primary_fragment(&self) -> bool { - true - } -} diff --git a/components/layout_2020/lib.rs b/components/layout_2020/lib.rs index 4af20789529..af5709eae38 100644 --- a/components/layout_2020/lib.rs +++ b/components/layout_2020/lib.rs @@ -6,13 +6,7 @@ pub mod context; pub mod data; -mod fragment; pub mod opaque_node; pub mod query; pub mod traversal; pub mod wrapper; - -// For unit tests: -pub use crate::fragment::Fragment; - -use servo_arc::Arc as ServoArc; From eaf08ef5171e3dde522f4e9ced2bb465390559b0 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Wed, 21 Aug 2019 12:14:57 +0200 Subject: [PATCH 2/5] Remove some more code --- components/layout_thread_2020/lib.rs | 134 +++++---------------------- 1 file changed, 22 insertions(+), 112 deletions(-) diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index ec31d858b9f..e313a5a6546 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -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; @@ -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, - /// 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, @@ -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, @@ -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, @@ -853,21 +843,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 +885,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 +934,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 +965,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 +990,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 +1022,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,15 +1066,6 @@ 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 token = { let shared = @@ -1143,31 +1076,8 @@ impl LayoutThread { }; 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::( - &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, ); } From be0e84b30f5b3ffa708b81348e66748ec43dfc8a Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Thu, 22 Aug 2019 13:58:36 +0200 Subject: [PATCH 3/5] Rename RecalcStyleAndConstructFlows to RecalcStyle It doesn't construct flows anymore. --- components/layout_2020/traversal.rs | 8 ++++---- components/layout_thread_2020/lib.rs | 15 +++++---------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/components/layout_2020/traversal.rs b/components/layout_2020/traversal.rs index 5aefdc6e939..bdc2c095026 100644 --- a/components/layout_2020/traversal.rs +++ b/components/layout_2020/traversal.rs @@ -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 for RecalcStyleAndConstructFlows<'a> +impl<'a, E> DomTraversal for RecalcStyle<'a> where E: TElement, E::ConcreteNode: LayoutNode, diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index e313a5a6546..47feb8e1f2a 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -44,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}; @@ -1066,19 +1066,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 traversal = RecalcStyleAndConstructFlows::new(layout_context); + let traversal = RecalcStyle::new(layout_context); let token = { - let shared = - >::shared_context( - &traversal, - ); - RecalcStyleAndConstructFlows::pre_traverse(element, shared) + let shared = DomTraversal::::shared_context(&traversal); + RecalcStyle::pre_traverse(element, shared) }; if token.should_traverse() { - driver::traverse_dom::( - &traversal, token, None, - ); + driver::traverse_dom(&traversal, token, None); } for element in elements_with_snapshot { From 86904757e66285412de162483f3ba2a6db35403e Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 9 Sep 2019 19:23:36 +0200 Subject: [PATCH 4/5] Import files from Victor https://github.com/SimonSapin/victor/tree/fdb11f3e87f6d2d59170d10169fa6deb94e53b94 --- components/layout_2020/dom_traversal.rs | 344 +++++++++++++ components/layout_2020/element_data.rs | 19 + components/layout_2020/flow/construct.rs | 625 +++++++++++++++++++++++ components/layout_2020/flow/float.rs | 18 + components/layout_2020/flow/inline.rs | 345 +++++++++++++ components/layout_2020/flow/mod.rs | 439 ++++++++++++++++ components/layout_2020/flow/root.rs | 122 +++++ components/layout_2020/fragments.rs | 116 +++++ components/layout_2020/geom.rs | 330 ++++++++++++ components/layout_2020/positioned.rs | 328 ++++++++++++ components/layout_2020/primitives.rs | 28 + components/layout_2020/replaced.rs | 14 + 12 files changed, 2728 insertions(+) create mode 100644 components/layout_2020/dom_traversal.rs create mode 100644 components/layout_2020/element_data.rs create mode 100644 components/layout_2020/flow/construct.rs create mode 100644 components/layout_2020/flow/float.rs create mode 100644 components/layout_2020/flow/inline.rs create mode 100644 components/layout_2020/flow/mod.rs create mode 100644 components/layout_2020/flow/root.rs create mode 100644 components/layout_2020/fragments.rs create mode 100644 components/layout_2020/geom.rs create mode 100644 components/layout_2020/positioned.rs create mode 100644 components/layout_2020/primitives.rs create mode 100644 components/layout_2020/replaced.rs diff --git a/components/layout_2020/dom_traversal.rs b/components/layout_2020/dom_traversal.rs new file mode 100644 index 00000000000..848e89ba564 --- /dev/null +++ b/components/layout_2020/dom_traversal.rs @@ -0,0 +1,344 @@ +use super::*; +use crate::dom::{Document, NodeData, NodeId}; +use crate::style::StyleSet; +use atomic_refcell::AtomicRefMut; + +pub(super) struct Context<'a> { + pub document: &'a Document, + pub author_styles: &'a StyleSet, +} + +#[derive(Copy, Clone)] +pub(super) enum WhichPseudoElement { + Before, + After, +} + +pub(super) enum Contents { + /// Refers to a DOM subtree, plus `::before` and `::after` pseudo-elements. + OfElement(NodeId), + + /// Example: an `` element. + /// + Replaced(ReplacedContent), + + /// Content of a `::before` or `::after` pseudo-element this is being generated. + /// + OfPseudoElement(Vec), +} + +pub(super) enum NonReplacedContents { + OfElement(NodeId), + OfPseudoElement(Vec), +} + +pub(super) enum PseudoElementContentItem { + Text(String), + Replaced(ReplacedContent), +} + +pub(super) trait TraversalHandler<'dom> { + fn handle_text(&mut self, text: &str, parent_style: &Arc); + + /// Or pseudo-element + fn handle_element( + &mut self, + style: &Arc, + display: DisplayGeneratingBox, + contents: Contents, + box_slot: BoxSlot<'dom>, + ); +} + +fn traverse_children_of<'dom>( + parent_element: NodeId, + parent_element_style: &Arc, + context: &'dom Context, + handler: &mut impl TraversalHandler<'dom>, +) { + traverse_pseudo_element( + WhichPseudoElement::Before, + parent_element, + parent_element_style, + context, + handler, + ); + + let mut next = context.document[parent_element].first_child; + while let Some(child) = next { + match &context.document[child].data { + NodeData::Document + | NodeData::Doctype { .. } + | NodeData::Comment { .. } + | NodeData::ProcessingInstruction { .. } => {} + NodeData::Text { contents } => { + handler.handle_text(contents, parent_element_style); + } + NodeData::Element(_) => traverse_element(child, parent_element_style, context, handler), + } + next = context.document[child].next_sibling + } + + traverse_pseudo_element( + WhichPseudoElement::After, + parent_element, + &parent_element_style, + context, + handler, + ); +} + +fn traverse_element<'dom>( + element_id: NodeId, + parent_element_style: &ComputedValues, + context: &'dom Context, + handler: &mut impl TraversalHandler<'dom>, +) { + let style = style_for_element( + context.author_styles, + context.document, + element_id, + Some(parent_element_style), + ); + match style.box_.display { + Display::None => context.unset_boxes_in_subtree(element_id), + Display::Contents => { + if ReplacedContent::for_element(element_id, context).is_some() { + // `display: content` on a replaced element computes to `display: none` + // + context.unset_boxes_in_subtree(element_id) + } else { + context.layout_data_mut(element_id).self_box = Some(LayoutBox::DisplayContents); + traverse_children_of(element_id, &style, context, handler) + } + } + Display::GeneratingBox(display) => handler.handle_element( + &style, + display, + match ReplacedContent::for_element(element_id, context) { + Some(replaced) => Contents::Replaced(replaced), + None => Contents::OfElement(element_id), + }, + context.element_box_slot(element_id), + ), + } +} + +fn traverse_pseudo_element<'dom>( + which: WhichPseudoElement, + element: NodeId, + element_style: &ComputedValues, + context: &'dom Context, + handler: &mut impl TraversalHandler<'dom>, +) { + if let Some(style) = pseudo_element_style(which, element, element_style, context) { + match style.box_.display { + Display::None => context.unset_pseudo_element_box(element, which), + Display::Contents => { + context.unset_pseudo_element_box(element, which); + let items = generate_pseudo_element_content(&style, element, context); + traverse_pseudo_element_contents(&style, items, handler); + } + Display::GeneratingBox(display) => { + let items = generate_pseudo_element_content(&style, element, context); + let contents = Contents::OfPseudoElement(items); + let box_slot = context.pseudo_element_box_slot(element, which); + handler.handle_element(&style, display, contents, box_slot); + } + } + } +} + +fn traverse_pseudo_element_contents<'dom>( + pseudo_element_style: &Arc, + items: Vec, + handler: &mut impl TraversalHandler<'dom>, +) { + let mut anonymous_style = None; + for item in items { + match item { + PseudoElementContentItem::Text(text) => { + handler.handle_text(&text, pseudo_element_style) + } + PseudoElementContentItem::Replaced(contents) => { + let item_style = anonymous_style.get_or_insert_with(|| { + ComputedValues::anonymous_inheriting_from(Some(pseudo_element_style)) + }); + let display_inline = DisplayGeneratingBox::OutsideInside { + outside: DisplayOutside::Inline, + inside: DisplayInside::Flow, + }; + // `display` is not inherited, so we get the initial value + debug_assert!(item_style.box_.display == Display::GeneratingBox(display_inline)); + handler.handle_element( + item_style, + display_inline, + Contents::Replaced(contents), + // We don’t keep pointers to boxes generated by contents of pseudo-elements + BoxSlot::dummy(), + ) + } + } + } +} + +impl std::convert::TryFrom for NonReplacedContents { + type Error = ReplacedContent; + + fn try_from(contents: Contents) -> Result { + match contents { + Contents::OfElement(id) => Ok(NonReplacedContents::OfElement(id)), + Contents::OfPseudoElement(items) => Ok(NonReplacedContents::OfPseudoElement(items)), + Contents::Replaced(replaced) => Err(replaced), + } + } +} + +impl std::convert::From for Contents { + fn from(contents: NonReplacedContents) -> Self { + match contents { + NonReplacedContents::OfElement(id) => Contents::OfElement(id), + NonReplacedContents::OfPseudoElement(items) => Contents::OfPseudoElement(items), + } + } +} + +impl NonReplacedContents { + pub fn traverse<'dom>( + self, + inherited_style: &Arc, + context: &'dom Context, + handler: &mut impl TraversalHandler<'dom>, + ) { + match self { + NonReplacedContents::OfElement(id) => { + traverse_children_of(id, inherited_style, context, handler) + } + NonReplacedContents::OfPseudoElement(items) => { + traverse_pseudo_element_contents(inherited_style, items, handler) + } + } + } +} + +fn pseudo_element_style( + _which: WhichPseudoElement, + _element: NodeId, + _element_style: &ComputedValues, + _context: &Context, +) -> Option> { + // FIXME: run the cascade, then return None for `content: normal` or `content: none` + // https://drafts.csswg.org/css2/generate.html#content + None +} + +fn generate_pseudo_element_content( + _pseudo_element_style: &ComputedValues, + _element: NodeId, + _context: &Context, +) -> Vec { + let _ = PseudoElementContentItem::Text; + let _ = PseudoElementContentItem::Replaced; + unimplemented!() +} + +pub(super) struct BoxSlot<'dom> { + slot: Option>>, +} + +impl<'dom> BoxSlot<'dom> { + pub fn new(mut slot: AtomicRefMut<'dom, Option>) -> Self { + *slot = None; + Self { slot: Some(slot) } + } + + pub fn dummy() -> Self { + Self { slot: None } + } + + pub fn set(mut self, box_: LayoutBox) { + if let Some(slot) = &mut self.slot { + **slot = Some(box_) + } + } +} + +impl Drop for BoxSlot<'_> { + fn drop(&mut self) { + if let Some(slot) = &mut self.slot { + assert!(slot.is_some(), "failed to set a layout box") + } + } +} + +impl Context<'_> { + fn layout_data_mut(&self, element_id: NodeId) -> AtomicRefMut { + self.document[element_id] + .as_element() + .unwrap() + .layout_data + .borrow_mut() + } + + fn element_box_slot(&self, element_id: NodeId) -> BoxSlot { + BoxSlot::new(AtomicRefMut::map( + self.layout_data_mut(element_id), + |data| &mut data.self_box, + )) + } + + fn pseudo_element_box_slot(&self, element_id: NodeId, which: WhichPseudoElement) -> BoxSlot { + BoxSlot::new(AtomicRefMut::map( + self.layout_data_mut(element_id), + |data| { + let pseudos = data.pseudo_elements.get_or_insert_with(Default::default); + match which { + WhichPseudoElement::Before => &mut pseudos.before, + WhichPseudoElement::After => &mut pseudos.after, + } + }, + )) + } + + fn unset_pseudo_element_box(&self, element_id: NodeId, which: WhichPseudoElement) { + if let Some(pseudos) = &mut self.layout_data_mut(element_id).pseudo_elements { + match which { + WhichPseudoElement::Before => pseudos.before = None, + WhichPseudoElement::After => pseudos.after = None, + } + } + } + + fn unset_boxes_in_subtree(&self, base_element: NodeId) { + let mut node_id = base_element; + loop { + let node = &self.document[node_id]; + if let Some(element_data) = node.as_element() { + let mut layout_data = element_data.layout_data.borrow_mut(); + layout_data.pseudo_elements = None; + if layout_data.self_box.take().is_some() { + // Only descend into children if we removed a box. + // If there wasn’t one, then descendants don’t have boxes either. + if let Some(child) = node.first_child { + node_id = child; + continue; + } + } + } + let mut next_is_a_sibling_of = node_id; + node_id = loop { + if let Some(sibling) = self.document[next_is_a_sibling_of].next_sibling { + break sibling; + } else { + next_is_a_sibling_of = node + .parent + .expect("reached the root while traversing only a subtree"); + } + }; + if next_is_a_sibling_of == base_element { + // Don’t go outside the subtree + return; + } + } + } +} diff --git a/components/layout_2020/element_data.rs b/components/layout_2020/element_data.rs new file mode 100644 index 00000000000..bda0d3cbe9e --- /dev/null +++ b/components/layout_2020/element_data.rs @@ -0,0 +1,19 @@ +use super::*; + +#[derive(Default)] +pub(crate) struct LayoutDataForElement { + pub(super) self_box: Option, + pub(super) pseudo_elements: Option>, +} + +#[derive(Default)] +pub(super) struct PseudoElementBoxes { + pub before: Option, + pub after: Option, +} + +pub(super) enum LayoutBox { + DisplayContents, + BlockLevel(Arc), + InlineLevel(Arc), +} diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs new file mode 100644 index 00000000000..4acfc93ce92 --- /dev/null +++ b/components/layout_2020/flow/construct.rs @@ -0,0 +1,625 @@ +use super::*; + +impl BlockFormattingContext { + pub fn construct<'a>( + context: &'a Context<'a>, + style: &'a Arc, + contents: NonReplacedContents, + ) -> Self { + let (contents, contains_floats) = BlockContainer::construct(context, style, contents); + Self { + contents, + contains_floats: contains_floats == ContainsFloats::Yes, + } + } +} + +enum IntermediateBlockLevelBox { + SameFormattingContextBlock { + style: Arc, + contents: IntermediateBlockContainer, + }, + Independent { + style: Arc, + display_inside: DisplayInside, + contents: Contents, + }, + OutOfFlowAbsolutelyPositionedBox { + style: Arc, + display_inside: DisplayInside, + contents: Contents, + }, + OutOfFlowFloatBox { + style: Arc, + display_inside: DisplayInside, + contents: Contents, + }, +} + +/// A block container that may still have to be constructed. +/// +/// Represents either the inline formatting context of an anonymous block +/// box or the yet-to-be-computed block container generated from the children +/// of a given element. +/// +/// Deferring allows using rayon’s `into_par_iter`. +enum IntermediateBlockContainer { + InlineFormattingContext(InlineFormattingContext), + Deferred { contents: NonReplacedContents }, +} + +/// A builder for a block container. +/// +/// This builder starts from the first child of a given DOM node +/// and does a preorder traversal of all of its inclusive siblings. +struct BlockContainerBuilder<'a> { + context: &'a Context<'a>, + block_container_style: &'a Arc, + + /// The list of block-level boxes of the final block container. + /// + /// Contains all the complete block level boxes we found traversing the tree + /// so far, if this is empty at the end of the traversal and the ongoing + /// inline formatting context is not empty, the block container establishes + /// an inline formatting context (see end of `build`). + /// + /// DOM nodes which represent block-level boxes are immediately pushed + /// to this list with their style without ever being traversed at this + /// point, instead we just move to their next sibling. If the DOM node + /// doesn't have a next sibling, we either reached the end of the container + /// root or there are ongoing inline-level boxes + /// (see `handle_block_level_element`). + block_level_boxes: Vec<(IntermediateBlockLevelBox, BoxSlot<'a>)>, + + /// The ongoing inline formatting context of the builder. + /// + /// Contains all the complete inline level boxes we found traversing the + /// tree so far. If a block-level box is found during traversal, + /// this inline formatting context is pushed as a block level box to + /// the list of block-level boxes of the builder + /// (see `end_ongoing_inline_formatting_context`). + ongoing_inline_formatting_context: InlineFormattingContext, + + /// The ongoing stack of inline boxes stack of the builder. + /// + /// Contains all the currently ongoing inline boxes we entered so far. + /// The traversal is at all times as deep in the tree as this stack is, + /// which is why the code doesn't need to keep track of the actual + /// container root (see `handle_inline_level_element`). + /// + /// Whenever the end of a DOM element that represents an inline box is + /// reached, the inline box at the top of this stack is complete and ready + /// to be pushed to the children of the next last ongoing inline box + /// the ongoing inline formatting context if the stack is now empty, + /// which means we reached the end of a child of the actual + /// container root (see `move_to_next_sibling`). + ongoing_inline_boxes_stack: Vec, + + /// 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>, + + /// Whether the resulting block container contains any float box. + contains_floats: ContainsFloats, +} + +impl BlockContainer { + pub fn construct<'a>( + context: &'a Context<'a>, + block_container_style: &'a Arc, + contents: NonReplacedContents, + ) -> (BlockContainer, ContainsFloats) { + let mut builder = BlockContainerBuilder { + context, + block_container_style, + block_level_boxes: Default::default(), + ongoing_inline_formatting_context: Default::default(), + ongoing_inline_boxes_stack: Default::default(), + anonymous_style: Default::default(), + contains_floats: Default::default(), + }; + + contents.traverse(block_container_style, context, &mut builder); + + debug_assert!(builder.ongoing_inline_boxes_stack.is_empty()); + + if !builder + .ongoing_inline_formatting_context + .inline_level_boxes + .is_empty() + { + if builder.block_level_boxes.is_empty() { + let container = BlockContainer::InlineFormattingContext( + builder.ongoing_inline_formatting_context, + ); + return (container, builder.contains_floats); + } + builder.end_ongoing_inline_formatting_context(); + } + + let mut contains_floats = builder.contains_floats; + let container = BlockContainer::BlockLevelBoxes( + builder + .block_level_boxes + .into_par_iter() + .mapfold_reduce_into( + &mut contains_floats, + |contains_floats, (intermediate, box_slot)| { + let (block_level_box, box_contains_floats) = intermediate.finish(context); + *contains_floats |= box_contains_floats; + box_slot.set(LayoutBox::BlockLevel(block_level_box.clone())); + block_level_box + }, + |left, right| *left |= right, + ) + .collect(), + ); + (container, contains_floats) + } +} + +impl<'a> TraversalHandler<'a> for BlockContainerBuilder<'a> { + fn handle_element( + &mut self, + style: &Arc, + display: DisplayGeneratingBox, + contents: Contents, + box_slot: BoxSlot<'a>, + ) { + match display { + DisplayGeneratingBox::OutsideInside { outside, inside } => match outside { + DisplayOutside::Inline => box_slot.set(LayoutBox::InlineLevel( + self.handle_inline_level_element(style, inside, contents), + )), + DisplayOutside::Block => { + // Floats and abspos cause blockification, so they only happen in this case. + // https://drafts.csswg.org/css2/visuren.html#dis-pos-flo + if style.box_.position.is_absolutely_positioned() { + self.handle_absolutely_positioned_element( + style.clone(), + inside, + contents, + box_slot, + ) + } else if style.box_.float.is_floating() { + self.handle_float_element(style.clone(), inside, contents, box_slot) + } else { + self.handle_block_level_element(style.clone(), inside, contents, box_slot) + } + } + }, + } + } + + fn handle_text(&mut self, input: &str, parent_style: &Arc) { + 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]) -> Option<&mut String> { + let last = inlines.last_mut()?; + if let InlineLevelBox::TextRun(_) = &**last { + // We never clone text run boxes, so the refcount is 1 and unwrap succeeds: + let last = Arc::get_mut(last).unwrap(); + if let InlineLevelBox::TextRun(TextRun { text, .. }) = last { + Some(text) + } else { + unreachable!() + } + } else { + None + } + } + + let mut new_text_run_contents; + let output; + if let Some(text) = last_text(inlines) { + // Append to the existing text run + new_text_run_contents = None; + output = text; + } else { + new_text_run_contents = Some(String::new()); + output = new_text_run_contents.as_mut().unwrap(); + } + + if leading_whitespace { + output.push(' ') + } + loop { + if let Some(i) = input.bytes().position(|b| b.is_ascii_whitespace()) { + let (non_whitespace, rest) = input.split_at(i); + output.push_str(non_whitespace); + output.push(' '); + if let Some(i) = rest.bytes().position(|b| !b.is_ascii_whitespace()) { + input = &rest[i..]; + } else { + break; + } + } else { + output.push_str(input); + break; + } + } + + if let Some(text) = new_text_run_contents { + let parent_style = parent_style.clone(); + inlines.push(Arc::new(InlineLevelBox::TextRun(TextRun { + parent_style, + text, + }))) + } + } + } +} + +impl<'a> BlockContainerBuilder<'a> { + /// Returns: + /// + /// * Whether this text run has preserved (non-collapsible) leading whitespace + /// * The contents starting at the first non-whitespace character (or the empty string) + fn handle_leading_whitespace<'text>(&mut self, text: &'text str) -> (bool, &'text str) { + // FIXME: this is only an approximation of + // https://drafts.csswg.org/css2/text.html#white-space-model + if !text.starts_with(|c: char| c.is_ascii_whitespace()) { + return (false, text); + } + let mut inline_level_boxes = self.current_inline_level_boxes().iter().rev(); + let mut stack = Vec::new(); + let preserved = loop { + match inline_level_boxes.next().map(|b| &**b) { + Some(InlineLevelBox::TextRun(r)) => break !r.text.ends_with(' '), + Some(InlineLevelBox::Atomic { .. }) => break false, + Some(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(_)) + | Some(InlineLevelBox::OutOfFlowFloatBox(_)) => {} + Some(InlineLevelBox::InlineBox(b)) => { + stack.push(inline_level_boxes); + inline_level_boxes = b.children.iter().rev() + } + None => { + if let Some(iter) = stack.pop() { + inline_level_boxes = iter + } else { + break false; // Paragraph start + } + } + } + }; + let text = text.trim_start_matches(|c: char| c.is_ascii_whitespace()); + (preserved, text) + } + + fn handle_inline_level_element( + &mut self, + style: &Arc, + display_inside: DisplayInside, + contents: Contents, + ) -> Arc { + let box_ = match contents.try_into() { + Err(replaced) => Arc::new(InlineLevelBox::Atomic { + style: style.clone(), + contents: replaced, + }), + Ok(non_replaced) => match display_inside { + DisplayInside::Flow => { + // Whatever happened before, we just found an inline level element, so + // all we need to do is to remember this ongoing inline level box. + self.ongoing_inline_boxes_stack.push(InlineBox { + style: style.clone(), + first_fragment: true, + last_fragment: false, + children: vec![], + }); + + NonReplacedContents::traverse(non_replaced, &style, self.context, self); + + let mut inline_box = self + .ongoing_inline_boxes_stack + .pop() + .expect("no ongoing inline level box found"); + inline_box.last_fragment = true; + Arc::new(InlineLevelBox::InlineBox(inline_box)) + } + DisplayInside::FlowRoot => { + // a.k.a. `inline-block` + unimplemented!() + } + }, + }; + self.current_inline_level_boxes().push(box_.clone()); + box_ + } + + fn handle_block_level_element( + &mut self, + style: Arc, + 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, + display_inside: DisplayInside, + contents: Contents, + box_slot: BoxSlot<'a>, + ) { + if !self.has_ongoing_inline_formatting_context() { + let box_ = IntermediateBlockLevelBox::OutOfFlowAbsolutelyPositionedBox { + style, + contents, + display_inside, + }; + self.block_level_boxes.push((box_, box_slot)) + } else { + let box_ = Arc::new(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox( + AbsolutelyPositionedBox { + contents: IndependentFormattingContext::construct( + self.context, + &style, + display_inside, + contents, + ), + style, + }, + )); + self.current_inline_level_boxes().push(box_.clone()); + box_slot.set(LayoutBox::InlineLevel(box_)) + } + } + + fn handle_float_element( + &mut self, + style: Arc, + display_inside: DisplayInside, + contents: Contents, + box_slot: BoxSlot<'a>, + ) { + self.contains_floats = ContainsFloats::Yes; + + if !self.has_ongoing_inline_formatting_context() { + let box_ = IntermediateBlockLevelBox::OutOfFlowFloatBox { + style, + contents, + display_inside, + }; + self.block_level_boxes.push((box_, box_slot)); + } else { + let box_ = Arc::new(InlineLevelBox::OutOfFlowFloatBox(FloatBox { + contents: IndependentFormattingContext::construct( + self.context, + &style, + display_inside, + contents, + ), + style, + })); + self.current_inline_level_boxes().push(box_.clone()); + box_slot.set(LayoutBox::InlineLevel(box_)) + } + } + + fn end_ongoing_inline_formatting_context(&mut self) { + assert!( + self.ongoing_inline_boxes_stack.is_empty(), + "there should be no ongoing inline level boxes", + ); + + if self + .ongoing_inline_formatting_context + .inline_level_boxes + .is_empty() + { + // There should never be an empty inline formatting context. + return; + } + + let block_container_style = self.block_container_style; + let anonymous_style = self.anonymous_style.get_or_insert_with(|| { + // If parent_style is None, the parent is the document node, + // in which case anonymous inline boxes should inherit their + // styles from initial values. + ComputedValues::anonymous_inheriting_from(Some(block_container_style)) + }); + + let box_ = IntermediateBlockLevelBox::SameFormattingContextBlock { + style: anonymous_style.clone(), + contents: IntermediateBlockContainer::InlineFormattingContext(take( + &mut self.ongoing_inline_formatting_context, + )), + }; + self.block_level_boxes.push((box_, BoxSlot::dummy())) + } + + fn current_inline_level_boxes(&mut self) -> &mut Vec> { + match self.ongoing_inline_boxes_stack.last_mut() { + Some(last) => &mut last.children, + None => &mut self.ongoing_inline_formatting_context.inline_level_boxes, + } + } + + fn has_ongoing_inline_formatting_context(&self) -> bool { + !self + .ongoing_inline_formatting_context + .inline_level_boxes + .is_empty() + || !self.ongoing_inline_boxes_stack.is_empty() + } +} + +impl IntermediateBlockLevelBox { + fn finish(self, context: &Context) -> (Arc, ContainsFloats) { + match self { + IntermediateBlockLevelBox::SameFormattingContextBlock { style, contents } => { + let (contents, contains_floats) = contents.finish(context, &style); + let block_level_box = + Arc::new(BlockLevelBox::SameFormattingContextBlock { contents, style }); + (block_level_box, contains_floats) + } + IntermediateBlockLevelBox::Independent { + style, + display_inside, + contents, + } => { + let contents = IndependentFormattingContext::construct( + context, + &style, + display_inside, + contents, + ); + ( + Arc::new(BlockLevelBox::Independent { style, contents }), + ContainsFloats::No, + ) + } + IntermediateBlockLevelBox::OutOfFlowAbsolutelyPositionedBox { + style, + display_inside, + contents, + } => { + let block_level_box = Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox( + AbsolutelyPositionedBox { + contents: IndependentFormattingContext::construct( + context, + &style, + display_inside, + contents, + ), + style: style, + }, + )); + (block_level_box, ContainsFloats::No) + } + IntermediateBlockLevelBox::OutOfFlowFloatBox { + style, + display_inside, + contents, + } => { + let contents = IndependentFormattingContext::construct( + context, + &style, + display_inside, + contents, + ); + let block_level_box = Arc::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox { + contents, + style, + })); + (block_level_box, ContainsFloats::Yes) + } + } + } +} + +impl IntermediateBlockContainer { + fn finish( + self, + context: &Context, + style: &Arc, + ) -> (BlockContainer, ContainsFloats) { + match self { + IntermediateBlockContainer::Deferred { contents } => { + BlockContainer::construct(context, style, contents) + } + IntermediateBlockContainer::InlineFormattingContext(ifc) => { + // If that inline formatting context contained any float, those + // were already taken into account during the first phase of + // box construction. + ( + BlockContainer::InlineFormattingContext(ifc), + ContainsFloats::No, + ) + } + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(in crate::layout) enum ContainsFloats { + No, + Yes, +} + +impl std::ops::BitOrAssign for ContainsFloats { + fn bitor_assign(&mut self, other: Self) { + if other == ContainsFloats::Yes { + *self = ContainsFloats::Yes; + } + } +} + +impl Default for ContainsFloats { + fn default() -> Self { + ContainsFloats::No + } +} diff --git a/components/layout_2020/flow/float.rs b/components/layout_2020/flow/float.rs new file mode 100644 index 00000000000..1c06b5cf2da --- /dev/null +++ b/components/layout_2020/flow/float.rs @@ -0,0 +1,18 @@ +use super::*; + +#[derive(Debug)] +pub(in crate::layout) struct FloatBox { + pub style: Arc, + pub contents: IndependentFormattingContext, +} + +/// Data kept during layout about the floats in a given block formatting context. +pub(in crate::layout) struct FloatContext { + // TODO +} + +impl FloatContext { + pub fn new() -> Self { + FloatContext {} + } +} diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs new file mode 100644 index 00000000000..5c39bb9385b --- /dev/null +++ b/components/layout_2020/flow/inline.rs @@ -0,0 +1,345 @@ +use super::*; +use crate::fonts::BITSTREAM_VERA_SANS; +use crate::text::ShapedSegment; + +#[derive(Debug, Default)] +pub(in crate::layout) struct InlineFormattingContext { + pub(super) inline_level_boxes: Vec>, +} + +#[derive(Debug)] +pub(in crate::layout) enum InlineLevelBox { + InlineBox(InlineBox), + TextRun(TextRun), + OutOfFlowAbsolutelyPositionedBox(AbsolutelyPositionedBox), + OutOfFlowFloatBox(FloatBox), + Atomic { + style: Arc, + // FIXME: this should be IndependentFormattingContext: + contents: ReplacedContent, + }, +} + +#[derive(Debug)] +pub(in crate::layout) struct InlineBox { + pub style: Arc, + pub first_fragment: bool, + pub last_fragment: bool, + pub children: Vec>, +} + +/// https://www.w3.org/TR/css-display-3/#css-text-run +#[derive(Debug)] +pub(in crate::layout) struct TextRun { + pub parent_style: Arc, + pub text: String, +} + +struct InlineNestingLevelState<'box_tree> { + remaining_boxes: std::slice::Iter<'box_tree, Arc>, + fragments_so_far: Vec, + inline_start: Length, + max_block_size_of_fragments_so_far: Length, +} + +struct PartialInlineBoxFragment<'box_tree> { + style: Arc, + start_corner: Vec2, + padding: Sides, + border: Sides, + margin: Sides, + 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>, + current_nesting_level: InlineNestingLevelState<'box_tree>, +} + +struct LinesBoxes { + boxes: Vec, + 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>, + ) -> FlowChildren { + let mut ifc = InlineFormattingContextState { + containing_block, + partial_inline_boxes_stack: Vec::new(), + line_boxes: LinesBoxes { + boxes: Vec::new(), + next_line_block_position: Length::zero(), + }, + inline_position: Length::zero(), + current_nesting_level: InlineNestingLevelState { + remaining_boxes: self.inline_level_boxes.iter(), + fragments_so_far: Vec::with_capacity(self.inline_level_boxes.len()), + inline_start: Length::zero(), + max_block_size_of_fragments_so_far: Length::zero(), + }, + }; + loop { + if let Some(child) = ifc.current_nesting_level.remaining_boxes.next() { + match &**child { + InlineLevelBox::InlineBox(inline) => { + let partial = inline.start_layout(&mut ifc); + ifc.partial_inline_boxes_stack.push(partial) + } + InlineLevelBox::TextRun(run) => run.layout(&mut ifc), + InlineLevelBox::Atomic { style: _, contents } => { + // FIXME + match *contents {} + } + InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => { + let initial_start_corner = match box_.style.specified_display { + Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { + outside, + inside: _, + }) => Vec2 { + inline: match outside { + DisplayOutside::Inline => ifc.inline_position, + DisplayOutside::Block => Length::zero(), + }, + block: ifc.line_boxes.next_line_block_position, + }, + Display::Contents => { + panic!("display:contents does not generate an abspos box") + } + Display::None => panic!("display:none does not generate an abspos box"), + }; + absolutely_positioned_fragments + .push(box_.layout(initial_start_corner, tree_rank)); + } + InlineLevelBox::OutOfFlowFloatBox(_box_) => { + // TODO + continue; + } + } + } else + // Reached the end of ifc.remaining_boxes + if let Some(mut partial) = ifc.partial_inline_boxes_stack.pop() { + partial.finish_layout( + &mut ifc.current_nesting_level, + &mut ifc.inline_position, + false, + ); + ifc.current_nesting_level = partial.parent_nesting_level + } else { + ifc.line_boxes + .finish_line(&mut ifc.current_nesting_level, containing_block); + return FlowChildren { + fragments: ifc.line_boxes.boxes, + block_size: ifc.line_boxes.next_line_block_position, + collapsible_margins_in_children: CollapsedBlockMargins::zero(), + }; + } + } + } +} + +impl LinesBoxes { + fn finish_line( + &mut self, + top_nesting_level: &mut InlineNestingLevelState, + containing_block: &ContainingBlock, + ) { + let start_corner = Vec2 { + inline: Length::zero(), + block: self.next_line_block_position, + }; + let size = Vec2 { + inline: containing_block.inline_size, + block: std::mem::replace( + &mut top_nesting_level.max_block_size_of_fragments_so_far, + Length::zero(), + ), + }; + self.next_line_block_position += size.block; + self.boxes.push(Fragment::Anonymous(AnonymousFragment { + children: take(&mut top_nesting_level.fragments_so_far), + rect: Rect { start_corner, size }, + mode: containing_block.mode, + })) + } +} + +impl InlineBox { + fn start_layout<'box_tree>( + &'box_tree self, + ifc: &mut InlineFormattingContextState<'box_tree, '_>, + ) -> PartialInlineBoxFragment<'box_tree> { + let style = self.style.clone(); + let cbis = ifc.containing_block.inline_size; + let mut padding = style.padding().percentages_relative_to(cbis); + let mut border = style.border_width().percentages_relative_to(cbis); + let mut margin = style + .margin() + .percentages_relative_to(cbis) + .auto_is(Length::zero); + if self.first_fragment { + ifc.inline_position += padding.inline_start + border.inline_start + margin.inline_start; + } else { + padding.inline_start = Length::zero(); + border.inline_start = Length::zero(); + margin.inline_start = Length::zero(); + } + let mut start_corner = Vec2 { + block: padding.block_start + border.block_start + margin.block_start, + inline: ifc.inline_position - ifc.current_nesting_level.inline_start, + }; + start_corner += &relative_adjustement( + &style, + ifc.containing_block.inline_size, + ifc.containing_block.block_size, + ); + PartialInlineBoxFragment { + style, + start_corner, + padding, + border, + margin, + last_box_tree_fragment: self.last_fragment, + parent_nesting_level: std::mem::replace( + &mut ifc.current_nesting_level, + InlineNestingLevelState { + remaining_boxes: self.children.iter(), + fragments_so_far: Vec::with_capacity(self.children.len()), + inline_start: ifc.inline_position, + max_block_size_of_fragments_so_far: Length::zero(), + }, + ), + } + } +} + +impl<'box_tree> PartialInlineBoxFragment<'box_tree> { + fn finish_layout( + &mut self, + nesting_level: &mut InlineNestingLevelState, + inline_position: &mut Length, + at_line_break: bool, + ) { + let mut fragment = BoxFragment { + style: self.style.clone(), + children: take(&mut nesting_level.fragments_so_far), + content_rect: Rect { + size: Vec2 { + inline: *inline_position - self.start_corner.inline, + block: nesting_level.max_block_size_of_fragments_so_far, + }, + start_corner: self.start_corner.clone(), + }, + padding: self.padding.clone(), + border: self.border.clone(), + margin: self.margin.clone(), + block_margins_collapsed_with_children: CollapsedBlockMargins::zero(), + }; + let last_fragment = self.last_box_tree_fragment && !at_line_break; + if last_fragment { + *inline_position += fragment.padding.inline_end + + fragment.border.inline_end + + fragment.margin.inline_end; + } else { + fragment.padding.inline_end = Length::zero(); + fragment.border.inline_end = Length::zero(); + fragment.margin.inline_end = Length::zero(); + } + self.parent_nesting_level + .max_block_size_of_fragments_so_far + .max_assign( + fragment.content_rect.size.block + + fragment.padding.block_sum() + + fragment.border.block_sum() + + fragment.margin.block_sum(), + ); + self.parent_nesting_level + .fragments_so_far + .push(Fragment::Box(fragment)); + } +} + +impl TextRun { + fn layout(&self, ifc: &mut InlineFormattingContextState) { + let available = ifc.containing_block.inline_size - ifc.inline_position; + let mut chars = self.text.chars(); + loop { + let mut shaped = ShapedSegment::new_with_naive_shaping(BITSTREAM_VERA_SANS.clone()); + let mut last_break_opportunity = None; + loop { + let next = chars.next(); + if matches!(next, Some(' ') | None) { + let inline_size = self.parent_style.font.font_size * shaped.advance_width; + if inline_size > available { + if let Some((state, iter)) = last_break_opportunity.take() { + shaped.restore(&state); + chars = iter; + } + break; + } + } + if let Some(ch) = next { + if ch == ' ' { + last_break_opportunity = Some((shaped.save(), chars.clone())) + } + shaped.append_char(ch).unwrap() + } else { + break; + } + } + let inline_size = self.parent_style.font.font_size * shaped.advance_width; + // https://www.w3.org/TR/CSS2/visudet.html#propdef-line-height + // 'normal': + // “set the used value to a "reasonable" value based on the font of the element.” + let line_height = self.parent_style.font.font_size.0 * 1.2; + let content_rect = Rect { + start_corner: Vec2 { + block: Length::zero(), + inline: ifc.inline_position - ifc.current_nesting_level.inline_start, + }, + size: Vec2 { + block: line_height, + inline: inline_size, + }, + }; + ifc.inline_position += inline_size; + ifc.current_nesting_level + .max_block_size_of_fragments_so_far + .max_assign(line_height); + ifc.current_nesting_level + .fragments_so_far + .push(Fragment::Text(TextFragment { + parent_style: self.parent_style.clone(), + content_rect, + text: shaped, + })); + if chars.as_str().is_empty() { + break; + } else { + // New line + ifc.current_nesting_level.inline_start = Length::zero(); + let mut nesting_level = &mut ifc.current_nesting_level; + for partial in ifc.partial_inline_boxes_stack.iter_mut().rev() { + partial.finish_layout(nesting_level, &mut ifc.inline_position, true); + partial.start_corner.inline = Length::zero(); + partial.padding.inline_start = Length::zero(); + partial.border.inline_start = Length::zero(); + partial.margin.inline_start = Length::zero(); + partial.parent_nesting_level.inline_start = Length::zero(); + nesting_level = &mut partial.parent_nesting_level; + } + ifc.line_boxes + .finish_line(nesting_level, ifc.containing_block); + ifc.inline_position = Length::zero(); + } + } + } +} diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs new file mode 100644 index 00000000000..a810dfb37fa --- /dev/null +++ b/components/layout_2020/flow/mod.rs @@ -0,0 +1,439 @@ +//! Flow layout, also known as block-and-inline layout. + +use super::*; +use rayon::prelude::*; +use rayon_croissant::ParallelIteratorExt; + +mod construct; +mod float; +mod inline; +mod root; + +pub(super) use construct::*; +pub(super) use float::*; +pub(super) use inline::*; + +#[derive(Debug)] +pub(super) struct BlockFormattingContext { + pub contents: BlockContainer, + pub contains_floats: bool, +} + +#[derive(Debug)] +pub(super) enum BlockContainer { + BlockLevelBoxes(Vec>), + InlineFormattingContext(InlineFormattingContext), +} + +#[derive(Debug)] +pub(super) enum BlockLevelBox { + SameFormattingContextBlock { + style: Arc, + contents: BlockContainer, + }, + OutOfFlowAbsolutelyPositionedBox(AbsolutelyPositionedBox), + OutOfFlowFloatBox(FloatBox), + Independent { + style: Arc, + contents: IndependentFormattingContext, + }, +} + +pub(super) struct FlowChildren { + pub fragments: Vec, + 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>, + ) -> 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>, + 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], + containing_block: &ContainingBlock, + tree_rank: usize, + absolutely_positioned_fragments: &mut Vec>, + 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>, + 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>, + style: &Arc, + block_level_kind: BlockLevelKind, + layout_contents: impl FnOnce( + &ContainingBlock, + &mut Vec>, + CollapsibleWithParentStartMargin, + ) -> FlowChildren, +) -> BoxFragment { + let cbis = containing_block.inline_size; + let padding = style.padding().percentages_relative_to(cbis); + let border = style.border_width().percentages_relative_to(cbis); + let mut computed_margin = style.margin().percentages_relative_to(cbis); + let pb = &padding + &border; + let box_size = style.box_size(); + let inline_size = box_size.inline.percentage_relative_to(cbis); + if let LengthOrAuto::Length(is) = inline_size { + let inline_margins = cbis - is - pb.inline_sum(); + use LengthOrAuto::*; + match ( + &mut computed_margin.inline_start, + &mut computed_margin.inline_end, + ) { + (s @ &mut Auto, e @ &mut Auto) => { + *s = Length(inline_margins / 2.); + *e = Length(inline_margins / 2.); + } + (s @ &mut Auto, _) => { + *s = Length(inline_margins); + } + (_, e @ &mut Auto) => { + *e = Length(inline_margins); + } + (_, e @ _) => { + // Either the inline-end margin is auto, + // or we’re over-constrained and we do as if it were. + *e = Length(inline_margins); + } + } + } + let margin = computed_margin.auto_is(Length::zero); + let mut block_margins_collapsed_with_children = CollapsedBlockMargins::from_margin(&margin); + let inline_size = inline_size.auto_is(|| cbis - pb.inline_sum() - margin.inline_sum()); + let block_size = match box_size.block { + LengthOrPercentageOrAuto::Length(l) => LengthOrAuto::Length(l), + LengthOrPercentageOrAuto::Percentage(p) => containing_block.block_size.map(|cbbs| cbbs * p), + LengthOrPercentageOrAuto::Auto => LengthOrAuto::Auto, + }; + let containing_block_for_children = ContainingBlock { + inline_size, + block_size, + mode: style.writing_mode(), + }; + // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows + assert_eq!( + containing_block.mode, containing_block_for_children.mode, + "Mixed writing modes are not supported yet" + ); + let this_start_margin_can_collapse_with_children = CollapsibleWithParentStartMargin( + block_level_kind == BlockLevelKind::SameFormattingContextBlock + && pb.block_start == Length::zero(), + ); + let this_end_margin_can_collapse_with_children = (block_level_kind, pb.block_end, block_size) + == ( + BlockLevelKind::SameFormattingContextBlock, + Length::zero(), + LengthOrAuto::Auto, + ); + let mut nested_abspos = vec![]; + let mut flow_children = layout_contents( + &containing_block_for_children, + if style.box_.position.is_relatively_positioned() { + &mut nested_abspos + } else { + absolutely_positioned_fragments + }, + this_start_margin_can_collapse_with_children, + ); + if this_start_margin_can_collapse_with_children.0 { + block_margins_collapsed_with_children + .start + .adjoin_assign(&flow_children.collapsible_margins_in_children.start); + if flow_children + .collapsible_margins_in_children + .collapsed_through + { + block_margins_collapsed_with_children + .start + .adjoin_assign(&std::mem::replace( + &mut flow_children.collapsible_margins_in_children.end, + CollapsedMargin::zero(), + )); + } + } + if this_end_margin_can_collapse_with_children { + block_margins_collapsed_with_children + .end + .adjoin_assign(&flow_children.collapsible_margins_in_children.end); + } else { + flow_children.block_size += flow_children.collapsible_margins_in_children.end.solve(); + } + block_margins_collapsed_with_children.collapsed_through = + this_start_margin_can_collapse_with_children.0 + && this_end_margin_can_collapse_with_children + && flow_children + .collapsible_margins_in_children + .collapsed_through; + let relative_adjustement = relative_adjustement(style, inline_size, block_size); + let block_size = block_size.auto_is(|| flow_children.block_size); + let content_rect = Rect { + start_corner: Vec2 { + block: pb.block_start + relative_adjustement.block, + inline: pb.inline_start + relative_adjustement.inline + margin.inline_start, + }, + size: Vec2 { + block: block_size, + inline: inline_size, + }, + }; + if style.box_.position.is_relatively_positioned() { + AbsolutelyPositionedFragment::in_positioned_containing_block( + &nested_abspos, + &mut flow_children.fragments, + &content_rect.size, + &padding, + containing_block_for_children.mode, + ) + } + BoxFragment { + style: style.clone(), + children: flow_children.fragments, + content_rect, + padding, + border, + margin, + block_margins_collapsed_with_children, + } +} diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs new file mode 100644 index 00000000000..443969c2acb --- /dev/null +++ b/components/layout_2020/flow/root.rs @@ -0,0 +1,122 @@ +use super::*; + +impl crate::dom::Document { + pub(crate) fn layout( + &self, + viewport: crate::primitives::Size, + ) -> Vec { + BoxTreeRoot::construct(self).layout(viewport) + } +} + +struct BoxTreeRoot(BlockFormattingContext); + +impl BoxTreeRoot { + pub fn construct(document: &dom::Document) -> Self { + let author_styles = &document.parse_stylesheets(); + let context = Context { + document, + author_styles, + }; + let root_element = document.root_element(); + let style = style_for_element(context.author_styles, context.document, root_element, None); + let (contains_floats, boxes) = construct_for_root_element(&context, root_element, style); + Self(BlockFormattingContext { + contains_floats: contains_floats == ContainsFloats::Yes, + contents: BlockContainer::BlockLevelBoxes(boxes), + }) + } +} + +fn construct_for_root_element( + context: &Context, + root_element: dom::NodeId, + style: Arc, +) -> (ContainsFloats, Vec>) { + let replaced = ReplacedContent::for_element(root_element, context); + + let display_inside = match style.box_.display { + Display::None => return (ContainsFloats::No, Vec::new()), + Display::Contents if replaced.is_some() => { + // 'display: contents' computes to 'none' for replaced elements + return (ContainsFloats::No, Vec::new()); + } + // https://drafts.csswg.org/css-display-3/#transformations + Display::Contents => DisplayInside::Flow, + // The root element is blockified, ignore DisplayOutside + Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside, + }; + + if let Some(replaced) = replaced { + let _box = match replaced {}; + #[allow(unreachable_code)] + { + return (ContainsFloats::No, vec![Arc::new(_box)]); + } + } + + let contents = IndependentFormattingContext::construct( + context, + &style, + display_inside, + Contents::OfElement(root_element), + ); + if style.box_.position.is_absolutely_positioned() { + ( + ContainsFloats::No, + vec![Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox( + AbsolutelyPositionedBox { style, contents }, + ))], + ) + } else if style.box_.float.is_floating() { + ( + ContainsFloats::Yes, + vec![Arc::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox { + contents, + style, + }))], + ) + } else { + ( + ContainsFloats::No, + vec![Arc::new(BlockLevelBox::Independent { style, contents })], + ) + } +} + +impl BoxTreeRoot { + fn layout(&self, viewport: crate::primitives::Size) -> Vec { + let initial_containing_block_size = Vec2 { + inline: Length { px: viewport.width }, + block: Length { + px: viewport.height, + }, + }; + + let initial_containing_block = ContainingBlock { + inline_size: initial_containing_block_size.inline, + block_size: LengthOrAuto::Length(initial_containing_block_size.block), + // FIXME: use the document’s mode: + // https://drafts.csswg.org/css-writing-modes/#principal-flow + mode: (WritingMode::HorizontalTb, Direction::Ltr), + }; + let dummy_tree_rank = 0; + let mut absolutely_positioned_fragments = vec![]; + let mut flow_children = self.0.layout( + &initial_containing_block, + dummy_tree_rank, + &mut absolutely_positioned_fragments, + ); + + let initial_containing_block = DefiniteContainingBlock { + size: initial_containing_block_size, + mode: initial_containing_block.mode, + }; + flow_children.fragments.par_extend( + absolutely_positioned_fragments + .par_iter() + .map(|a| a.layout(&initial_containing_block)), + ); + flow_children.fragments + } +} diff --git a/components/layout_2020/fragments.rs b/components/layout_2020/fragments.rs new file mode 100644 index 00000000000..f2a4dcd2986 --- /dev/null +++ b/components/layout_2020/fragments.rs @@ -0,0 +1,116 @@ +use super::*; +use crate::text::ShapedSegment; + +pub(crate) enum Fragment { + Box(BoxFragment), + Anonymous(AnonymousFragment), + Text(TextFragment), +} + +pub(crate) struct BoxFragment { + pub style: Arc, + pub children: Vec, + + /// From the containing block’s start corner…? + /// This might be broken when the containing block is in a different writing mode: + /// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows + pub content_rect: Rect, + + pub padding: Sides, + pub border: Sides, + pub margin: Sides, + + 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, + pub children: Vec, + pub mode: (WritingMode, Direction), +} + +pub(crate) struct TextFragment { + pub parent_style: Arc, + pub content_rect: Rect, + 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 { + self.content_rect + .inflate(&self.padding) + .inflate(&self.border) + } +} + +impl CollapsedBlockMargins { + pub fn from_margin(margin: &Sides) -> 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 + } +} diff --git a/components/layout_2020/geom.rs b/components/layout_2020/geom.rs new file mode 100644 index 00000000000..2032a3e72ab --- /dev/null +++ b/components/layout_2020/geom.rs @@ -0,0 +1,330 @@ +pub(crate) use crate::style::values::Length; +use crate::style::values::{LengthOrAuto, LengthOrPercentage, LengthOrPercentageOrAuto}; + +pub(crate) mod physical { + #[derive(Debug, Clone)] + pub(crate) struct Vec2 { + pub x: T, + pub y: T, + } + + #[derive(Debug, Clone)] + pub(crate) struct Rect { + pub top_left: Vec2, + pub size: Vec2, + } + + #[derive(Debug, Clone)] + pub(crate) struct Sides { + pub top: T, + pub left: T, + pub bottom: T, + pub right: T, + } +} + +pub(crate) mod flow_relative { + #[derive(Debug, Clone)] + pub(crate) struct Vec2 { + pub inline: T, + pub block: T, + } + + #[derive(Debug, Clone)] + pub(crate) struct Rect { + pub start_corner: Vec2, + pub size: Vec2, + } + + #[derive(Debug, Clone)] + pub(crate) struct Sides { + pub inline_start: T, + pub inline_end: T, + pub block_start: T, + pub block_end: T, + } +} + +use crate::style::values::{Direction, WritingMode}; +use std::ops::{Add, AddAssign, Sub}; + +impl Add<&'_ physical::Vec2> for &'_ physical::Vec2 +where + T: Add + Copy, +{ + type Output = physical::Vec2; + + fn add(self, other: &'_ physical::Vec2) -> Self::Output { + physical::Vec2 { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +impl physical::Vec2 { + pub fn size_to_flow_relative(&self, mode: (WritingMode, Direction)) -> flow_relative::Vec2 { + // 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 Add<&'_ flow_relative::Vec2> for &'_ flow_relative::Vec2 +where + T: Add + Copy, +{ + type Output = flow_relative::Vec2; + + fn add(self, other: &'_ flow_relative::Vec2) -> Self::Output { + flow_relative::Vec2 { + inline: self.inline + other.inline, + block: self.block + other.block, + } + } +} + +impl AddAssign<&'_ flow_relative::Vec2> for flow_relative::Vec2 +where + T: AddAssign + Copy, +{ + fn add_assign(&mut self, other: &'_ flow_relative::Vec2) { + self.inline += other.inline; + self.block += other.block; + } +} + +impl flow_relative::Vec2 { + pub fn zero() -> Self { + Self { + inline: Length::zero(), + block: Length::zero(), + } + } +} + +impl flow_relative::Sides { + 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 { + pub fn zero() -> Self { + Self { + start_corner: flow_relative::Vec2::zero(), + size: flow_relative::Vec2::zero(), + } + } +} + +impl flow_relative::Vec2 { + pub fn size_to_physical(&self, mode: (WritingMode, Direction)) -> physical::Vec2 { + // 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> for crate::primitives::Point { + fn from(v: physical::Vec2) -> Self { + crate::primitives::Point::from_lengths(v.x.into(), v.y.into()) + } +} + +impl physical::Sides { + pub fn to_flow_relative(&self, mode: (WritingMode, Direction)) -> flow_relative::Sides { + use Direction::*; + use WritingMode::*; + + // https://drafts.csswg.org/css-writing-modes/#logical-to-physical + let (bs, be) = match mode.0 { + HorizontalTb => (&self.top, &self.bottom), + VerticalRl | SidewaysRl => (&self.right, &self.left), + VerticalLr | SidewaysLr => (&self.left, &self.right), + }; + let (is, ie) = match mode { + (HorizontalTb, Ltr) => (&self.left, &self.right), + (HorizontalTb, Rtl) => (&self.right, &self.left), + (VerticalRl, Ltr) | (SidewaysRl, Ltr) | (VerticalLr, Ltr) | (SidewaysLr, Rtl) => { + (&self.top, &self.bottom) + } + (VerticalRl, Rtl) | (SidewaysRl, Rtl) | (VerticalLr, Rtl) | (SidewaysLr, Ltr) => { + (&self.bottom, &self.top) + } + }; + flow_relative::Sides { + inline_start: is.clone(), + inline_end: ie.clone(), + block_start: bs.clone(), + block_end: be.clone(), + } + } +} + +impl flow_relative::Sides { + pub fn map(&self, f: impl Fn(&T) -> U) -> flow_relative::Sides { + 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( + &self, + inline_f: impl Fn(&T) -> U, + block_f: impl Fn(&T) -> U, + ) -> flow_relative::Sides { + 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 + where + T: Clone, + { + flow_relative::Vec2 { + inline: self.inline_start.clone(), + block: self.block_start.clone(), + } + } +} + +impl flow_relative::Sides { + pub fn percentages_relative_to(&self, basis: Length) -> flow_relative::Sides { + self.map(|s| s.percentage_relative_to(basis)) + } +} + +impl flow_relative::Sides { + pub fn percentages_relative_to(&self, basis: Length) -> flow_relative::Sides { + self.map(|s| s.percentage_relative_to(basis)) + } +} + +impl flow_relative::Sides { + pub fn auto_is(&self, f: impl Fn() -> Length) -> flow_relative::Sides { + self.map(|s| s.auto_is(&f)) + } +} + +impl Add<&'_ flow_relative::Sides> for &'_ flow_relative::Sides +where + T: Add + Copy, +{ + type Output = flow_relative::Sides; + + fn add(self, other: &'_ flow_relative::Sides) -> 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 flow_relative::Rect { + pub fn inflate(&self, sides: &flow_relative::Sides) -> Self + where + T: Add + Copy, + T: Sub + 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, + ) -> physical::Rect + 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 physical::Rect { + pub fn translate(&self, by: &physical::Vec2) -> Self + where + T: Add + Copy, + { + physical::Rect { + top_left: &self.top_left + by, + size: self.size.clone(), + } + } +} + +impl From> for crate::primitives::Rect { + fn from(r: physical::Rect) -> Self { + crate::primitives::Rect { + origin: crate::primitives::Point::new(r.top_left.x.px, r.top_left.y.px), + size: crate::primitives::Size::new(r.size.x.px, r.size.y.px), + } + } +} diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs new file mode 100644 index 00000000000..9794d1acdae --- /dev/null +++ b/components/layout_2020/positioned.rs @@ -0,0 +1,328 @@ +use super::*; +use rayon::prelude::*; + +#[derive(Debug)] +pub(super) struct AbsolutelyPositionedBox { + pub style: Arc, + pub contents: IndependentFormattingContext, +} + +#[derive(Debug)] +pub(super) struct AbsolutelyPositionedFragment<'box_> { + absolutely_positioned_box: &'box_ AbsolutelyPositionedBox, + + /// The rank of the child from which this absolutely positioned fragment + /// came from, when doing the layout of a block container. Used to compute + /// static positions when going up the tree. + pub(super) tree_rank: usize, + + pub(super) inline_start: AbsoluteBoxOffsets, + inline_size: LengthOrPercentageOrAuto, + + pub(super) block_start: AbsoluteBoxOffsets, + block_size: LengthOrPercentageOrAuto, +} + +#[derive(Clone, Copy, Debug)] +pub(super) enum AbsoluteBoxOffsets { + StaticStart { start: Length }, + Start { start: NonStatic }, + End { end: NonStatic }, + Both { start: NonStatic, end: NonStatic }, +} + +impl AbsolutelyPositionedBox { + pub(super) fn layout<'a>( + &'a self, + initial_start_corner: Vec2, + tree_rank: usize, + ) -> AbsolutelyPositionedFragment { + let style = &self.style; + let box_offsets = style.box_offsets(); + let box_size = style.box_size(); + + let inline_size = box_size.inline; + let block_size = box_size.block; + + fn absolute_box_offsets( + initial_static_start: Length, + start: LengthOrPercentageOrAuto, + end: LengthOrPercentageOrAuto, + ) -> AbsoluteBoxOffsets { + match (start.non_auto(), end.non_auto()) { + (None, None) => AbsoluteBoxOffsets::StaticStart { + start: initial_static_start, + }, + (Some(start), Some(end)) => AbsoluteBoxOffsets::Both { start, end }, + (None, Some(end)) => AbsoluteBoxOffsets::End { end }, + (Some(start), None) => AbsoluteBoxOffsets::Start { start }, + } + } + + let inline_start = absolute_box_offsets( + initial_start_corner.inline, + box_offsets.inline_start, + box_offsets.inline_end, + ); + let block_start = absolute_box_offsets( + initial_start_corner.block, + box_offsets.block_start, + box_offsets.block_end, + ); + + AbsolutelyPositionedFragment { + absolutely_positioned_box: self, + tree_rank, + inline_start, + inline_size, + block_start, + block_size, + } + } +} + +impl<'a> AbsolutelyPositionedFragment<'a> { + pub(super) fn in_positioned_containing_block( + absolute: &[Self], + fragments: &mut Vec, + content_rect_size: &Vec2, + padding: &Sides, + mode: (WritingMode, Direction), + ) { + if absolute.is_empty() { + return; + } + let padding_rect = Rect { + size: content_rect_size.clone(), + // Ignore the content rect’s position in its own containing block: + start_corner: Vec2::zero(), + } + .inflate(&padding); + let containing_block = DefiniteContainingBlock { + size: padding_rect.size.clone(), + mode, + }; + fragments.push(Fragment::Anonymous(AnonymousFragment { + children: absolute + .par_iter() + .map(|a| a.layout(&containing_block)) + .collect(), + rect: padding_rect, + mode, + })) + } + + pub(super) fn layout(&self, containing_block: &DefiniteContainingBlock) -> Fragment { + let style = &self.absolutely_positioned_box.style; + let cbis = containing_block.size.inline; + let cbbs = containing_block.size.block; + + let padding = style.padding().percentages_relative_to(cbis); + let border = style.border_width().percentages_relative_to(cbis); + let computed_margin = style.margin().percentages_relative_to(cbis); + let pb = &padding + &border; + + enum Anchor { + Start(Length), + End(Length), + } + + fn solve_axis( + containing_size: Length, + padding_border_sum: Length, + computed_margin_start: LengthOrAuto, + computed_margin_end: LengthOrAuto, + solve_margins: impl FnOnce(Length) -> (Length, Length), + box_offsets: AbsoluteBoxOffsets, + size: LengthOrPercentageOrAuto, + ) -> (Anchor, LengthOrAuto, Length, Length) { + let size = size.percentage_relative_to(containing_size); + match box_offsets { + AbsoluteBoxOffsets::StaticStart { start } => ( + Anchor::Start(start), + size, + computed_margin_start.auto_is(Length::zero), + computed_margin_end.auto_is(Length::zero), + ), + AbsoluteBoxOffsets::Start { start } => ( + Anchor::Start(start.percentage_relative_to(containing_size)), + size, + computed_margin_start.auto_is(Length::zero), + computed_margin_end.auto_is(Length::zero), + ), + AbsoluteBoxOffsets::End { end } => ( + Anchor::End(end.percentage_relative_to(containing_size)), + size, + computed_margin_start.auto_is(Length::zero), + computed_margin_end.auto_is(Length::zero), + ), + AbsoluteBoxOffsets::Both { start, end } => { + let start = start.percentage_relative_to(containing_size); + let end = end.percentage_relative_to(containing_size); + + let mut margin_start = computed_margin_start.auto_is(Length::zero); + let mut margin_end = computed_margin_end.auto_is(Length::zero); + + let size = if let LengthOrAuto::Length(size) = size { + use LengthOrAuto::Auto; + let margins = containing_size - start - end - padding_border_sum - size; + match (computed_margin_start, computed_margin_end) { + (Auto, Auto) => { + let (s, e) = solve_margins(margins); + margin_start = s; + margin_end = e; + } + (Auto, LengthOrAuto::Length(end)) => { + margin_start = margins - end; + } + (LengthOrAuto::Length(start), Auto) => { + margin_end = margins - start; + } + (LengthOrAuto::Length(_), LengthOrAuto::Length(_)) => {} + } + size + } else { + // FIXME(nox): What happens if that is negative? + containing_size + - start + - end + - padding_border_sum + - margin_start + - margin_end + }; + ( + Anchor::Start(start), + LengthOrAuto::Length(size), + margin_start, + margin_end, + ) + } + } + } + + let (inline_anchor, inline_size, margin_inline_start, margin_inline_end) = solve_axis( + cbis, + pb.inline_sum(), + computed_margin.inline_start, + computed_margin.inline_end, + |margins| { + if margins.px >= 0. { + (margins / 2., margins / 2.) + } else { + (Length::zero(), margins) + } + }, + self.inline_start, + self.inline_size, + ); + + let (block_anchor, block_size, margin_block_start, margin_block_end) = solve_axis( + cbis, + pb.block_sum(), + computed_margin.block_start, + computed_margin.block_end, + |margins| (margins / 2., margins / 2.), + self.block_start, + self.block_size, + ); + + let margin = Sides { + inline_start: margin_inline_start, + inline_end: margin_inline_end, + block_start: margin_block_start, + block_end: margin_block_end, + }; + + let inline_size = inline_size.auto_is(|| { + let available_size = match inline_anchor { + Anchor::Start(start) => cbis - start - pb.inline_sum() - margin.inline_sum(), + Anchor::End(end) => cbis - end - pb.inline_sum() - margin.inline_sum(), + }; + + // FIXME(nox): shrink-to-fit. + available_size + }); + + let containing_block_for_children = ContainingBlock { + inline_size, + block_size, + mode: style.writing_mode(), + }; + // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows + assert_eq!( + containing_block.mode, containing_block_for_children.mode, + "Mixed writing modes are not supported yet" + ); + let dummy_tree_rank = 0; + let mut absolutely_positioned_fragments = vec![]; + let mut flow_children = self.absolutely_positioned_box.contents.layout( + &containing_block_for_children, + dummy_tree_rank, + &mut absolutely_positioned_fragments, + ); + + let inline_start = match inline_anchor { + Anchor::Start(start) => start + pb.inline_start + margin.inline_start, + Anchor::End(end) => cbbs - end - pb.inline_end - margin.inline_end - inline_size, + }; + + let block_size = block_size.auto_is(|| flow_children.block_size); + let block_start = match block_anchor { + Anchor::Start(start) => start + pb.block_start + margin.block_start, + Anchor::End(end) => cbbs - end - pb.block_end - margin.block_end - block_size, + }; + + let content_rect = Rect { + start_corner: Vec2 { + inline: inline_start, + block: block_start, + }, + size: Vec2 { + inline: inline_size, + block: block_size, + }, + }; + + AbsolutelyPositionedFragment::in_positioned_containing_block( + &absolutely_positioned_fragments, + &mut flow_children.fragments, + &content_rect.size, + &padding, + containing_block_for_children.mode, + ); + + Fragment::Box(BoxFragment { + style: style.clone(), + children: flow_children.fragments, + content_rect, + padding, + border, + margin, + block_margins_collapsed_with_children: CollapsedBlockMargins::zero(), + }) + } +} + +pub(super) fn adjust_static_positions( + absolutely_positioned_fragments: &mut [AbsolutelyPositionedFragment], + child_fragments: &mut [Fragment], + tree_rank_in_parent: usize, +) { + for abspos_fragment in absolutely_positioned_fragments { + let child_fragment_rect = match &child_fragments[abspos_fragment.tree_rank] { + Fragment::Box(b) => &b.content_rect, + Fragment::Anonymous(a) => &a.rect, + _ => unreachable!(), + }; + + abspos_fragment.tree_rank = tree_rank_in_parent; + + if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.inline_start { + *start += child_fragment_rect.start_corner.inline; + } + + if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.block_start { + *start += child_fragment_rect.start_corner.block; + } + } +} diff --git a/components/layout_2020/primitives.rs b/components/layout_2020/primitives.rs new file mode 100644 index 00000000000..0991478ac06 --- /dev/null +++ b/components/layout_2020/primitives.rs @@ -0,0 +1,28 @@ +use crate::text; + +/// Origin at top-left corner, unit `1px` +pub struct CssPx; + +pub use euclid::point2 as point; +pub use euclid::rect; +pub type Length = euclid::Length; +pub type Point = euclid::TypedPoint2D; +pub type Size = euclid::TypedSize2D; +pub type Rect = euclid::TypedRect; +pub type SideOffsets = euclid::TypedSideOffsets2D; +pub type Scale = euclid::TypedScale; + +#[derive(Copy, Clone, PartialEq)] +pub struct RGBA(pub f32, pub f32, pub f32, pub f32); + +pub struct TextRun<'a> { + pub segment: &'a text::ShapedSegment, + pub font_size: Length, + pub origin: Point, +} + +impl From for RGBA { + fn from(c: cssparser::RGBA) -> Self { + RGBA(c.red_f32(), c.green_f32(), c.blue_f32(), c.alpha_f32()) + } +} diff --git a/components/layout_2020/replaced.rs b/components/layout_2020/replaced.rs new file mode 100644 index 00000000000..312326c84af --- /dev/null +++ b/components/layout_2020/replaced.rs @@ -0,0 +1,14 @@ +use super::*; +use crate::dom::NodeId; + +#[derive(Debug)] +pub(super) enum ReplacedContent { + // Not implemented yet +} + +impl ReplacedContent { + pub fn for_element(_element: NodeId, _context: &Context) -> Option { + // FIXME: implement etc. + None + } +} From 4444c5a2adfec909e165497ca331c09e02ee54cc Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Mon, 9 Sep 2019 17:21:26 +0200 Subject: [PATCH 5/5] =?UTF-8?q?Import=20victor's=20layout=20system=20?= =?UTF-8?q?=F0=9F=8D=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mailmap | 1 + Cargo.lock | 21 ++ components/layout/model.rs | 2 +- components/layout_2020/Cargo.toml | 5 + components/layout_2020/context.rs | 3 + components/layout_2020/data.rs | 4 + components/layout_2020/dom_traversal.rs | 388 +++++++++++---------- components/layout_2020/element_data.rs | 8 +- components/layout_2020/flow/construct.rs | 342 ++++++++++-------- components/layout_2020/flow/float.rs | 12 +- components/layout_2020/flow/inline.rs | 160 +++------ components/layout_2020/flow/mod.rs | 113 +++--- components/layout_2020/flow/root.rs | 227 ++++++------ components/layout_2020/fragments.rs | 15 +- components/layout_2020/geom.rs | 58 +-- components/layout_2020/lib.rs | 148 ++++++++ components/layout_2020/positioned.rs | 89 +++-- components/layout_2020/primitives.rs | 28 -- components/layout_2020/replaced.rs | 13 +- components/layout_2020/style_ext.rs | 129 +++++++ components/layout_thread_2020/lib.rs | 1 + components/style/style_adjuster.rs | 3 + components/style/values/computed/length.rs | 110 +++++- components/style/values/generics/length.rs | 33 ++ components/style/values/specified/box.rs | 69 +++- 25 files changed, 1270 insertions(+), 712 deletions(-) delete mode 100644 components/layout_2020/primitives.rs create mode 100644 components/layout_2020/style_ext.rs diff --git a/.mailmap b/.mailmap index ead257b2c87..38629db02df 100644 --- a/.mailmap +++ b/.mailmap @@ -6,6 +6,7 @@ Agoston Szepessy Alexander Popiak Alexandrov Sergey Antonio de Luna +Anthony Ramine Askeing Yen (fyen) Attila Dusnoki bd339 diff --git a/Cargo.lock b/Cargo.lock index 1d73ed16de3..3ae201b281f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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,12 +2330,15 @@ 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)", + "servo_arc 0.1.1", "servo_url 0.0.1", "style 0.0.1", "style_traits 0.0.1", @@ -2901,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" @@ -3692,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" @@ -6052,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)" = "" "checksum mozjs_sys 0.67.0 (git+https://github.com/servo/mozjs?rev=2bf4912a6ff1f5ea333afb1cb53dfe9361157b14)" = "" @@ -6122,6 +6142,7 @@ dependencies = [ "checksum raqote 0.6.2-alpha.0 (git+https://github.com/jrmuizel/raqote)" = "" "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" diff --git a/components/layout/model.rs b/components/layout/model.rs index 17c3b625af3..e6be75e45a4 100644 --- a/components/layout/model.rs +++ b/components/layout/model.rs @@ -502,7 +502,7 @@ pub fn style_length(style_length: Size, container_size: Option) -> 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()))) }, } } diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml index 296d069c7e1..af6b53ace4b 100644 --- a/components/layout_2020/Cargo.toml +++ b/components/layout_2020/Cargo.toml @@ -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,12 +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_url = {path = "../url"} style = {path = "../style", features = ["servo", "servo-layout-2020"]} style_traits = {path = "../style_traits"} diff --git a/components/layout_2020/context.rs b/components/layout_2020/context.rs index 72398d85a80..0e96797f4d7 100644 --- a/components/layout_2020/context.rs +++ b/components/layout_2020/context.rs @@ -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, } impl<'a> LayoutContext<'a> { diff --git a/components/layout_2020/data.rs b/components/layout_2020/data.rs index c406a1bc4e7..d8aa8cf77ac 100644 --- a/components/layout_2020/data.rs +++ b/components/layout_2020/data.rs @@ -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, } impl StyleAndLayoutData { pub fn new() -> Self { Self { style_data: StyleData::new(), + layout_data: Default::default(), } } } diff --git a/components/layout_2020/dom_traversal.rs b/components/layout_2020/dom_traversal.rs index 848e89ba564..0e9e18a739e 100644 --- a/components/layout_2020/dom_traversal.rs +++ b/components/layout_2020/dom_traversal.rs @@ -1,22 +1,28 @@ -use super::*; -use crate::dom::{Document, NodeData, NodeId}; -use crate::style::StyleSet; +/* 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; -pub(super) struct Context<'a> { - pub document: &'a Document, - pub author_styles: &'a StyleSet, -} - -#[derive(Copy, Clone)] +#[derive(Clone, Copy)] pub(super) enum WhichPseudoElement { Before, After, } -pub(super) enum Contents { +pub(super) enum Contents { /// Refers to a DOM subtree, plus `::before` and `::after` pseudo-elements. - OfElement(NodeId), + OfElement(Node), /// Example: an `` element. /// @@ -27,8 +33,8 @@ pub(super) enum Contents { OfPseudoElement(Vec), } -pub(super) enum NonReplacedContents { - OfElement(NodeId), +pub(super) enum NonReplacedContents { + OfElement(Node), OfPseudoElement(Vec), } @@ -37,206 +43,196 @@ pub(super) enum PseudoElementContentItem { Replaced(ReplacedContent), } -pub(super) trait TraversalHandler<'dom> { - fn handle_text(&mut self, text: &str, parent_style: &Arc); +pub(super) trait TraversalHandler { + fn handle_text(&mut self, text: String, parent_style: &Arc); /// Or pseudo-element fn handle_element( &mut self, style: &Arc, display: DisplayGeneratingBox, - contents: Contents, - box_slot: BoxSlot<'dom>, + contents: Contents, + box_slot: BoxSlot, ); } -fn traverse_children_of<'dom>( - parent_element: NodeId, - parent_element_style: &Arc, - context: &'dom Context, - handler: &mut impl TraversalHandler<'dom>, -) { - traverse_pseudo_element( - WhichPseudoElement::Before, - parent_element, - parent_element_style, - context, - handler, - ); +fn traverse_children_of<'dom, Node>( + parent_element: Node, + context: &SharedStyleContext, + handler: &mut impl TraversalHandler, +) where + Node: NodeExt<'dom>, +{ + traverse_pseudo_element(WhichPseudoElement::Before, parent_element, context, handler); - let mut next = context.document[parent_element].first_child; + let mut next = parent_element.first_child(); while let Some(child) = next { - match &context.document[child].data { - NodeData::Document - | NodeData::Doctype { .. } - | NodeData::Comment { .. } - | NodeData::ProcessingInstruction { .. } => {} - NodeData::Text { contents } => { - handler.handle_text(contents, parent_element_style); - } - NodeData::Element(_) => traverse_element(child, parent_element_style, context, handler), + 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 = context.document[child].next_sibling + next = child.next_sibling(); } - traverse_pseudo_element( - WhichPseudoElement::After, - parent_element, - &parent_element_style, - context, - handler, - ); + traverse_pseudo_element(WhichPseudoElement::After, parent_element, context, handler); } -fn traverse_element<'dom>( - element_id: NodeId, - parent_element_style: &ComputedValues, - context: &'dom Context, - handler: &mut impl TraversalHandler<'dom>, -) { - let style = style_for_element( - context.author_styles, - context.document, - element_id, - Some(parent_element_style), - ); - match style.box_.display { - Display::None => context.unset_boxes_in_subtree(element_id), +fn traverse_element<'dom, Node>( + element: Node, + context: &SharedStyleContext, + handler: &mut impl TraversalHandler, +) 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_id, context).is_some() { + if ReplacedContent::for_element(element, context).is_some() { // `display: content` on a replaced element computes to `display: none` // - context.unset_boxes_in_subtree(element_id) + element.unset_boxes_in_subtree() } else { - context.layout_data_mut(element_id).self_box = Some(LayoutBox::DisplayContents); - traverse_children_of(element_id, &style, context, handler) + 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_id, context) { - Some(replaced) => Contents::Replaced(replaced), - None => Contents::OfElement(element_id), - }, - context.element_box_slot(element_id), - ), + }, + 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>( +fn traverse_pseudo_element<'dom, Node>( which: WhichPseudoElement, - element: NodeId, - element_style: &ComputedValues, - context: &'dom Context, - handler: &mut impl TraversalHandler<'dom>, -) { - if let Some(style) = pseudo_element_style(which, element, element_style, context) { - match style.box_.display { - Display::None => context.unset_pseudo_element_box(element, which), + element: Node, + context: &SharedStyleContext, + handler: &mut impl TraversalHandler, +) 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 => { - context.unset_pseudo_element_box(element, which); + 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 = context.pseudo_element_box_slot(element, which); + let box_slot = element.pseudo_element_box_slot(which); handler.handle_element(&style, display, contents, box_slot); - } + }, } } } -fn traverse_pseudo_element_contents<'dom>( +fn traverse_pseudo_element_contents<'dom, Node>( pseudo_element_style: &Arc, items: Vec, - handler: &mut impl TraversalHandler<'dom>, -) { - let mut anonymous_style = None; + handler: &mut impl TraversalHandler, +) 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::Text(text) => handler.handle_text(text, pseudo_element_style), PseudoElementContentItem::Replaced(contents) => { - let item_style = anonymous_style.get_or_insert_with(|| { - ComputedValues::anonymous_inheriting_from(Some(pseudo_element_style)) - }); - let display_inline = DisplayGeneratingBox::OutsideInside { - outside: DisplayOutside::Inline, - inside: DisplayInside::Flow, - }; - // `display` is not inherited, so we get the initial value - debug_assert!(item_style.box_.display == Display::GeneratingBox(display_inline)); - handler.handle_element( - item_style, - display_inline, - Contents::Replaced(contents), - // We don’t keep pointers to boxes generated by contents of pseudo-elements - BoxSlot::dummy(), - ) - } + // 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 don’t keep pointers to boxes generated by contents of pseudo-elements + // BoxSlot::dummy(), + // ) + }, } } } -impl std::convert::TryFrom for NonReplacedContents { +impl std::convert::TryFrom> for NonReplacedContents { type Error = ReplacedContent; - fn try_from(contents: Contents) -> Result { + fn try_from(contents: Contents) -> Result { match contents { - Contents::OfElement(id) => Ok(NonReplacedContents::OfElement(id)), + Contents::OfElement(node) => Ok(NonReplacedContents::OfElement(node)), Contents::OfPseudoElement(items) => Ok(NonReplacedContents::OfPseudoElement(items)), Contents::Replaced(replaced) => Err(replaced), } } } -impl std::convert::From for Contents { - fn from(contents: NonReplacedContents) -> Self { +impl std::convert::From> for Contents { + fn from(contents: NonReplacedContents) -> Self { match contents { - NonReplacedContents::OfElement(id) => Contents::OfElement(id), + NonReplacedContents::OfElement(node) => Contents::OfElement(node), NonReplacedContents::OfPseudoElement(items) => Contents::OfPseudoElement(items), } } } -impl NonReplacedContents { - pub fn traverse<'dom>( +impl<'dom, Node> NonReplacedContents +where + Node: NodeExt<'dom>, +{ + pub(crate) fn traverse( self, inherited_style: &Arc, - context: &'dom Context, - handler: &mut impl TraversalHandler<'dom>, + context: &SharedStyleContext, + handler: &mut impl TraversalHandler, ) { match self { - NonReplacedContents::OfElement(id) => { - traverse_children_of(id, inherited_style, context, handler) - } + NonReplacedContents::OfElement(node) => traverse_children_of(node, context, handler), NonReplacedContents::OfPseudoElement(items) => { traverse_pseudo_element_contents(inherited_style, items, handler) - } + }, } } } -fn pseudo_element_style( +fn pseudo_element_style<'dom, Node>( _which: WhichPseudoElement, - _element: NodeId, - _element_style: &ComputedValues, - _context: &Context, -) -> Option> { + _element: Node, + _context: &SharedStyleContext, +) -> Option> +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( +fn generate_pseudo_element_content<'dom, Node>( _pseudo_element_style: &ComputedValues, - _element: NodeId, - _context: &Context, -) -> Vec { + _element: Node, + _context: &SharedStyleContext, +) -> Vec +where + Node: NodeExt<'dom>, +{ let _ = PseudoElementContentItem::Text; let _ = PseudoElementContentItem::Replaced; unimplemented!() @@ -271,37 +267,77 @@ impl Drop for BoxSlot<'_> { } } -impl Context<'_> { - fn layout_data_mut(&self, element_id: NodeId) -> AtomicRefMut { - self.document[element_id] - .as_element() +pub(crate) trait NodeExt<'dom>: 'dom + Copy + Send + Sync { + fn is_element(self) -> bool; + fn as_text(self) -> Option; + fn first_child(self) -> Option; + fn next_sibling(self) -> Option; + fn parent_node(self) -> Option; + fn style(self, context: &SharedStyleContext) -> Arc; + + fn layout_data_mut(&self) -> AtomicRefMut; + 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 { + if self.is_text_node() { + Some(self.to_threadsafe().node_text_content()) + } else { + None + } + } + + fn first_child(self) -> Option { + TNode::first_child(&self) + } + + fn next_sibling(self) -> Option { + TNode::next_sibling(&self) + } + + fn parent_node(self) -> Option { + TNode::next_sibling(&self) + } + + fn style(self, context: &SharedStyleContext) -> Arc { + self.to_threadsafe().style(context) + } + + fn layout_data_mut(&self) -> AtomicRefMut { + self.get_raw_data() + .map(|d| d.layout_data.borrow_mut()) .unwrap() - .layout_data - .borrow_mut() } - fn element_box_slot(&self, element_id: NodeId) -> BoxSlot { - BoxSlot::new(AtomicRefMut::map( - self.layout_data_mut(element_id), - |data| &mut data.self_box, - )) + fn element_box_slot(&self) -> BoxSlot { + BoxSlot::new(AtomicRefMut::map(self.layout_data_mut(), |data| { + &mut data.self_box + })) } - fn pseudo_element_box_slot(&self, element_id: NodeId, which: WhichPseudoElement) -> BoxSlot { - BoxSlot::new(AtomicRefMut::map( - self.layout_data_mut(element_id), - |data| { - let pseudos = data.pseudo_elements.get_or_insert_with(Default::default); - match which { - WhichPseudoElement::Before => &mut pseudos.before, - WhichPseudoElement::After => &mut pseudos.after, - } - }, - )) + fn 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, element_id: NodeId, which: WhichPseudoElement) { - if let Some(pseudos) = &mut self.layout_data_mut(element_id).pseudo_elements { + 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, @@ -309,34 +345,36 @@ impl Context<'_> { } } - fn unset_boxes_in_subtree(&self, base_element: NodeId) { - let mut node_id = base_element; + fn unset_boxes_in_subtree(self) { + let mut node = self; loop { - let node = &self.document[node_id]; - if let Some(element_data) = node.as_element() { - let mut layout_data = element_data.layout_data.borrow_mut(); - layout_data.pseudo_elements = None; - if layout_data.self_box.take().is_some() { + 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 wasn’t one, then descendants don’t have boxes either. - if let Some(child) = node.first_child { - node_id = child; + if let Some(child) = node.first_child() { + node = child; continue; } } } - let mut next_is_a_sibling_of = node_id; - node_id = loop { - if let Some(sibling) = self.document[next_is_a_sibling_of].next_sibling { + 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 + .parent_node() .expect("reached the root while traversing only a subtree"); } }; - if next_is_a_sibling_of == base_element { - // Don’t go outside the subtree + if next_is_a_sibling_of == self { + // Don’t go outside the subtree. return; } } diff --git a/components/layout_2020/element_data.rs b/components/layout_2020/element_data.rs index bda0d3cbe9e..17c27e1bfcd 100644 --- a/components/layout_2020/element_data.rs +++ b/components/layout_2020/element_data.rs @@ -1,4 +1,10 @@ -use super::*; +/* 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 { diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs index 4acfc93ce92..de590c950d4 100644 --- a/components/layout_2020/flow/construct.rs +++ b/components/layout_2020/flow/construct.rs @@ -1,10 +1,27 @@ -use super::*; +/* 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<'a>( - context: &'a Context<'a>, - style: &'a Arc, - contents: NonReplacedContents, + pub fn construct<'dom>( + context: &SharedStyleContext<'_>, + style: &Arc, + contents: NonReplacedContents>, ) -> Self { let (contents, contains_floats) = BlockContainer::construct(context, style, contents); Self { @@ -14,25 +31,25 @@ impl BlockFormattingContext { } } -enum IntermediateBlockLevelBox { +enum IntermediateBlockLevelBox { SameFormattingContextBlock { style: Arc, - contents: IntermediateBlockContainer, + contents: IntermediateBlockContainer, }, Independent { style: Arc, display_inside: DisplayInside, - contents: Contents, + contents: Contents, }, OutOfFlowAbsolutelyPositionedBox { style: Arc, display_inside: DisplayInside, - contents: Contents, + contents: Contents, }, OutOfFlowFloatBox { style: Arc, display_inside: DisplayInside, - contents: Contents, + contents: Contents, }, } @@ -43,18 +60,19 @@ enum IntermediateBlockLevelBox { /// of a given element. /// /// Deferring allows using rayon’s `into_par_iter`. -enum IntermediateBlockContainer { +enum IntermediateBlockContainer { InlineFormattingContext(InlineFormattingContext), - Deferred { contents: NonReplacedContents }, + Deferred { contents: NonReplacedContents }, } /// A builder for a block container. /// /// This builder starts from the first child of a given DOM node /// and does a preorder traversal of all of its inclusive siblings. -struct BlockContainerBuilder<'a> { - context: &'a Context<'a>, - block_container_style: &'a Arc, +struct BlockContainerBuilder<'dom, 'style, Node> { + context: &'style SharedStyleContext<'style>, + + block_container_style: &'style Arc, /// The list of block-level boxes of the final block container. /// @@ -69,7 +87,7 @@ struct BlockContainerBuilder<'a> { /// doesn't have a next sibling, we either reached the end of the container /// root or there are ongoing inline-level boxes /// (see `handle_block_level_element`). - block_level_boxes: Vec<(IntermediateBlockLevelBox, BoxSlot<'a>)>, + block_level_boxes: Vec<(IntermediateBlockLevelBox, BoxSlot<'dom>)>, /// The ongoing inline formatting context of the builder. /// @@ -104,10 +122,10 @@ struct BlockContainerBuilder<'a> { } impl BlockContainer { - pub fn construct<'a>( - context: &'a Context<'a>, - block_container_style: &'a Arc, - contents: NonReplacedContents, + pub fn construct<'dom, 'style>( + context: &SharedStyleContext<'style>, + block_container_style: &Arc, + contents: NonReplacedContents>, ) -> (BlockContainer, ContainsFloats) { let mut builder = BlockContainerBuilder { context, @@ -134,7 +152,8 @@ impl BlockContainer { ); return (container, builder.contains_floats); } - builder.end_ongoing_inline_formatting_context(); + // FIXME + // builder.end_ongoing_inline_formatting_context(); } let mut contains_floats = builder.contains_floats; @@ -144,10 +163,11 @@ impl BlockContainer { .into_par_iter() .mapfold_reduce_into( &mut contains_floats, - |contains_floats, (intermediate, box_slot)| { + |contains_floats, (intermediate, box_slot): (IntermediateBlockLevelBox<_>, BoxSlot<'_>)| { let (block_level_box, box_contains_floats) = intermediate.finish(context); *contains_floats |= box_contains_floats; - box_slot.set(LayoutBox::BlockLevel(block_level_box.clone())); + // FIXME + // box_slot.set(LayoutBox::BlockLevel(block_level_box.clone())); block_level_box }, |left, right| *left |= right, @@ -158,13 +178,16 @@ impl BlockContainer { } } -impl<'a> TraversalHandler<'a> for BlockContainerBuilder<'a> { +impl<'dom, Node> TraversalHandler for BlockContainerBuilder<'dom, '_, Node> +where + Node: NodeExt<'dom>, +{ fn handle_element( &mut self, style: &Arc, display: DisplayGeneratingBox, - contents: Contents, - box_slot: BoxSlot<'a>, + contents: Contents, + box_slot: BoxSlot, ) { match display { DisplayGeneratingBox::OutsideInside { outside, inside } => match outside { @@ -172,27 +195,29 @@ impl<'a> TraversalHandler<'a> for BlockContainerBuilder<'a> { 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) - } - } + // 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: &str, parent_style: &Arc) { - let (leading_whitespace, mut input) = self.handle_leading_whitespace(input); + fn handle_text(&mut self, input: String, parent_style: &Arc) { + 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 @@ -256,7 +281,10 @@ impl<'a> TraversalHandler<'a> for BlockContainerBuilder<'a> { } } -impl<'a> BlockContainerBuilder<'a> { +impl<'dom, Node> BlockContainerBuilder<'dom, '_, Node> +where + Node: NodeExt<'dom>, +{ /// Returns: /// /// * Whether this text run has preserved (non-collapsible) leading whitespace @@ -273,19 +301,19 @@ impl<'a> BlockContainerBuilder<'a> { 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::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()); @@ -296,7 +324,7 @@ impl<'a> BlockContainerBuilder<'a> { &mut self, style: &Arc, display_inside: DisplayInside, - contents: Contents, + contents: Contents, ) -> Arc { let box_ = match contents.try_into() { Err(replaced) => Arc::new(InlineLevelBox::Atomic { @@ -322,97 +350,99 @@ impl<'a> BlockContainerBuilder<'a> { .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_ } - fn handle_block_level_element( - &mut self, - style: Arc, - 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 - }); + // FIXME + // fn handle_block_level_element( + // &mut self, + // style: Arc, + // 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); - } + // 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)); - } + // 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(); + // // 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)) - } + // 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, display_inside: DisplayInside, - contents: Contents, - box_slot: BoxSlot<'a>, + contents: Contents, + box_slot: BoxSlot<'dom>, ) { if !self.has_ongoing_inline_formatting_context() { let box_ = IntermediateBlockLevelBox::OutOfFlowAbsolutelyPositionedBox { @@ -420,12 +450,12 @@ impl<'a> BlockContainerBuilder<'a> { contents, display_inside, }; - self.block_level_boxes.push((box_, box_slot)) + self.block_level_boxes.push((box_, box_slot)); } else { let box_ = Arc::new(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox( AbsolutelyPositionedBox { contents: IndependentFormattingContext::construct( - self.context, + unimplemented!(), &style, display_inside, contents, @@ -442,8 +472,8 @@ impl<'a> BlockContainerBuilder<'a> { &mut self, style: Arc, display_inside: DisplayInside, - contents: Contents, - box_slot: BoxSlot<'a>, + contents: Contents, + box_slot: BoxSlot<'dom>, ) { self.contains_floats = ContainsFloats::Yes; @@ -485,20 +515,21 @@ impl<'a> BlockContainerBuilder<'a> { } let block_container_style = self.block_container_style; - let anonymous_style = self.anonymous_style.get_or_insert_with(|| { - // If parent_style is None, the parent is the document node, - // in which case anonymous inline boxes should inherit their - // styles from initial values. - ComputedValues::anonymous_inheriting_from(Some(block_container_style)) - }); + // 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())) + // 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> { @@ -512,20 +543,26 @@ impl<'a> BlockContainerBuilder<'a> { !self .ongoing_inline_formatting_context .inline_level_boxes - .is_empty() - || !self.ongoing_inline_boxes_stack.is_empty() + .is_empty() || + !self.ongoing_inline_boxes_stack.is_empty() } } -impl IntermediateBlockLevelBox { - fn finish(self, context: &Context) -> (Arc, ContainsFloats) { +impl<'dom, Node> IntermediateBlockLevelBox +where + Node: NodeExt<'dom>, +{ + fn finish<'style>( + self, + context: &SharedStyleContext<'style>, + ) -> (Arc, 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, @@ -541,7 +578,7 @@ impl IntermediateBlockLevelBox { Arc::new(BlockLevelBox::Independent { style, contents }), ContainsFloats::No, ) - } + }, IntermediateBlockLevelBox::OutOfFlowAbsolutelyPositionedBox { style, display_inside, @@ -559,7 +596,7 @@ impl IntermediateBlockLevelBox { }, )); (block_level_box, ContainsFloats::No) - } + }, IntermediateBlockLevelBox::OutOfFlowFloatBox { style, display_inside, @@ -576,21 +613,24 @@ impl IntermediateBlockLevelBox { style, })); (block_level_box, ContainsFloats::Yes) - } + }, } } } -impl IntermediateBlockContainer { - fn finish( +impl<'dom, Node> IntermediateBlockContainer +where + Node: NodeExt<'dom>, +{ + fn finish<'style>( self, - context: &Context, + context: &SharedStyleContext<'style>, style: &Arc, ) -> (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 @@ -599,13 +639,13 @@ impl IntermediateBlockContainer { BlockContainer::InlineFormattingContext(ifc), ContainsFloats::No, ) - } + }, } } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub(in crate::layout) enum ContainsFloats { +pub(crate) enum ContainsFloats { No, Yes, } diff --git a/components/layout_2020/flow/float.rs b/components/layout_2020/flow/float.rs index 1c06b5cf2da..31fec0b79c0 100644 --- a/components/layout_2020/flow/float.rs +++ b/components/layout_2020/flow/float.rs @@ -1,13 +1,19 @@ -use super::*; +/* 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(in crate::layout) struct FloatBox { +pub(crate) struct FloatBox { pub style: Arc, pub contents: IndependentFormattingContext, } /// Data kept during layout about the floats in a given block formatting context. -pub(in crate::layout) struct FloatContext { +pub(crate) struct FloatContext { // TODO } diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs index 5c39bb9385b..59b822976ca 100644 --- a/components/layout_2020/flow/inline.rs +++ b/components/layout_2020/flow/inline.rs @@ -1,14 +1,27 @@ -use super::*; -use crate::fonts::BITSTREAM_VERA_SANS; -use crate::text::ShapedSegment; +/* 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(in crate::layout) struct InlineFormattingContext { +pub(crate) struct InlineFormattingContext { pub(super) inline_level_boxes: Vec>, } #[derive(Debug)] -pub(in crate::layout) enum InlineLevelBox { +pub(crate) enum InlineLevelBox { InlineBox(InlineBox), TextRun(TextRun), OutOfFlowAbsolutelyPositionedBox(AbsolutelyPositionedBox), @@ -21,7 +34,7 @@ pub(in crate::layout) enum InlineLevelBox { } #[derive(Debug)] -pub(in crate::layout) struct InlineBox { +pub(crate) struct InlineBox { pub style: Arc, pub first_fragment: bool, pub last_fragment: bool, @@ -30,7 +43,7 @@ pub(in crate::layout) struct InlineBox { /// https://www.w3.org/TR/css-display-3/#css-text-run #[derive(Debug)] -pub(in crate::layout) struct TextRun { +pub(crate) struct TextRun { pub parent_style: Arc, pub text: String, } @@ -93,36 +106,40 @@ impl InlineFormattingContext { InlineLevelBox::InlineBox(inline) => { let partial = inline.start_layout(&mut ifc); ifc.partial_inline_boxes_stack.push(partial) - } + }, InlineLevelBox::TextRun(run) => run.layout(&mut ifc), InlineLevelBox::Atomic { style: _, contents } => { // FIXME match *contents {} - } + }, InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => { - let initial_start_corner = match box_.style.specified_display { - Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { - outside, - inside: _, - }) => Vec2 { - inline: match outside { - DisplayOutside::Inline => ifc.inline_position, - DisplayOutside::Block => Length::zero(), + 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, }, - 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"), - }; + 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 @@ -180,7 +197,7 @@ impl InlineBox { let style = self.style.clone(); let cbis = ifc.containing_block.inline_size; let mut padding = style.padding().percentages_relative_to(cbis); - let mut border = style.border_width().percentages_relative_to(cbis); + let mut border = style.border_width(); let mut margin = style .margin() .percentages_relative_to(cbis) @@ -245,9 +262,9 @@ impl<'box_tree> PartialInlineBoxFragment<'box_tree> { }; 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; + *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(); @@ -256,10 +273,10 @@ impl<'box_tree> PartialInlineBoxFragment<'box_tree> { 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(), + fragment.content_rect.size.block + + fragment.padding.block_sum() + + fragment.border.block_sum() + + fragment.margin.block_sum(), ); self.parent_nesting_level .fragments_so_far @@ -268,78 +285,7 @@ impl<'box_tree> PartialInlineBoxFragment<'box_tree> { } impl TextRun { - fn layout(&self, ifc: &mut InlineFormattingContextState) { - let available = ifc.containing_block.inline_size - ifc.inline_position; - let mut chars = self.text.chars(); - loop { - let mut shaped = ShapedSegment::new_with_naive_shaping(BITSTREAM_VERA_SANS.clone()); - let mut last_break_opportunity = None; - loop { - let next = chars.next(); - if matches!(next, Some(' ') | None) { - let inline_size = self.parent_style.font.font_size * shaped.advance_width; - if inline_size > available { - if let Some((state, iter)) = last_break_opportunity.take() { - shaped.restore(&state); - chars = iter; - } - break; - } - } - if let Some(ch) = next { - if ch == ' ' { - last_break_opportunity = Some((shaped.save(), chars.clone())) - } - shaped.append_char(ch).unwrap() - } else { - break; - } - } - let inline_size = self.parent_style.font.font_size * shaped.advance_width; - // https://www.w3.org/TR/CSS2/visudet.html#propdef-line-height - // 'normal': - // “set the used value to a "reasonable" value based on the font of the element.” - let line_height = self.parent_style.font.font_size.0 * 1.2; - let content_rect = Rect { - start_corner: Vec2 { - block: Length::zero(), - inline: ifc.inline_position - ifc.current_nesting_level.inline_start, - }, - size: Vec2 { - block: line_height, - inline: inline_size, - }, - }; - ifc.inline_position += inline_size; - ifc.current_nesting_level - .max_block_size_of_fragments_so_far - .max_assign(line_height); - ifc.current_nesting_level - .fragments_so_far - .push(Fragment::Text(TextFragment { - parent_style: self.parent_style.clone(), - content_rect, - text: shaped, - })); - if chars.as_str().is_empty() { - break; - } else { - // New line - ifc.current_nesting_level.inline_start = Length::zero(); - let mut nesting_level = &mut ifc.current_nesting_level; - for partial in ifc.partial_inline_boxes_stack.iter_mut().rev() { - partial.finish_layout(nesting_level, &mut ifc.inline_position, true); - partial.start_corner.inline = Length::zero(); - partial.padding.inline_start = Length::zero(); - partial.border.inline_start = Length::zero(); - partial.margin.inline_start = Length::zero(); - partial.parent_nesting_level.inline_start = Length::zero(); - nesting_level = &mut partial.parent_nesting_level; - } - ifc.line_boxes - .finish_line(nesting_level, ifc.containing_block); - ifc.inline_position = Length::zero(); - } - } + fn layout(&self, _ifc: &mut InlineFormattingContextState) { + // TODO } } diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs index a810dfb37fa..ae92f70822c 100644 --- a/components/layout_2020/flow/mod.rs +++ b/components/layout_2020/flow/mod.rs @@ -1,32 +1,46 @@ +/* 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 super::*; -use rayon::prelude::*; +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; -mod inline; +pub mod inline; mod root; -pub(super) use construct::*; -pub(super) use float::*; -pub(super) use inline::*; - #[derive(Debug)] -pub(super) struct BlockFormattingContext { +pub(crate) struct BlockFormattingContext { pub contents: BlockContainer, pub contains_floats: bool, } #[derive(Debug)] -pub(super) enum BlockContainer { +pub(crate) enum BlockContainer { BlockLevelBoxes(Vec>), InlineFormattingContext(InlineFormattingContext), } #[derive(Debug)] -pub(super) enum BlockLevelBox { +pub(crate) enum BlockLevelBox { SameFormattingContextBlock { style: Arc, contents: BlockContainer, @@ -98,7 +112,7 @@ impl BlockContainer { ), BlockContainer::InlineFormattingContext(ifc) => { ifc.layout(containing_block, tree_rank, absolutely_positioned_fragments) - } + }, } } } @@ -115,9 +129,9 @@ fn layout_block_level_children<'a>( 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; + 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()); @@ -136,8 +150,8 @@ fn layout_block_level_children<'a>( .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; + 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 @@ -147,7 +161,7 @@ fn layout_block_level_children<'a>( 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. @@ -155,7 +169,7 @@ fn layout_block_level_children<'a>( assert_eq!(fragment.rect.size.block, Length::zero()); fragment.rect.start_corner.block += placement_state.current_block_direction_position; - } + }, _ => unreachable!(), } } @@ -261,12 +275,12 @@ impl BlockLevelBox { ) }, )) - } + }, 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, @@ -280,11 +294,11 @@ impl BlockLevelBox { 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)) - } + }, } } } @@ -310,43 +324,40 @@ fn layout_in_flow_non_replaced_block_level<'a>( ) -> BoxFragment { let cbis = containing_block.inline_size; let padding = style.padding().percentages_relative_to(cbis); - let border = style.border_width().percentages_relative_to(cbis); + let 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::Length(is) = inline_size { + if let LengthOrAuto::LengthPercentage(is) = inline_size { let inline_margins = cbis - is - pb.inline_sum(); - use LengthOrAuto::*; match ( &mut computed_margin.inline_start, &mut computed_margin.inline_end, ) { - (s @ &mut Auto, e @ &mut Auto) => { - *s = Length(inline_margins / 2.); - *e = Length(inline_margins / 2.); - } - (s @ &mut Auto, _) => { - *s = Length(inline_margins); - } - (_, e @ &mut Auto) => { - *e = Length(inline_margins); - } + (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 we’re over-constrained and we do as if it were. - *e = Length(inline_margins); - } + *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 = match box_size.block { - LengthOrPercentageOrAuto::Length(l) => LengthOrAuto::Length(l), - LengthOrPercentageOrAuto::Percentage(p) => containing_block.block_size.map(|cbbs| cbbs * p), - LengthOrPercentageOrAuto::Auto => LengthOrAuto::Auto, - }; + let 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, @@ -358,11 +369,11 @@ fn layout_in_flow_non_replaced_block_level<'a>( "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(), + 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) - == ( + let this_end_margin_can_collapse_with_children = (block_level_kind, pb.block_end, block_size) == + ( BlockLevelKind::SameFormattingContextBlock, Length::zero(), LengthOrAuto::Auto, @@ -370,7 +381,7 @@ fn layout_in_flow_non_replaced_block_level<'a>( let mut nested_abspos = vec![]; let mut flow_children = layout_contents( &containing_block_for_children, - if style.box_.position.is_relatively_positioned() { + if style.get_box().position == Position::Relative { &mut nested_abspos } else { absolutely_positioned_fragments @@ -401,9 +412,9 @@ fn layout_in_flow_non_replaced_block_level<'a>( 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 + 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); @@ -418,7 +429,7 @@ fn layout_in_flow_non_replaced_block_level<'a>( inline: inline_size, }, }; - if style.box_.position.is_relatively_positioned() { + if style.get_box().position == Position::Relative { AbsolutelyPositionedFragment::in_positioned_containing_block( &nested_abspos, &mut flow_children.fragments, diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs index 443969c2acb..08a23397506 100644 --- a/components/layout_2020/flow/root.rs +++ b/components/layout_2020/flow/root.rs @@ -1,122 +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::*; -impl crate::dom::Document { - pub(crate) fn layout( - &self, - viewport: crate::primitives::Size, - ) -> Vec { - BoxTreeRoot::construct(self).layout(viewport) - } -} +// FIXME +// impl crate::dom::Document { +// pub(crate) fn layout( +// &self, +// viewport: crate::geom::Size, +// ) -> Vec { +// BoxTreeRoot::construct(self).layout(viewport) +// } +// } struct BoxTreeRoot(BlockFormattingContext); -impl BoxTreeRoot { - pub fn construct(document: &dom::Document) -> Self { - let author_styles = &document.parse_stylesheets(); - let context = Context { - document, - author_styles, - }; - let root_element = document.root_element(); - let style = style_for_element(context.author_styles, context.document, root_element, None); - let (contains_floats, boxes) = construct_for_root_element(&context, root_element, style); - Self(BlockFormattingContext { - contains_floats: contains_floats == ContainsFloats::Yes, - contents: BlockContainer::BlockLevelBoxes(boxes), - }) - } -} +// 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, -) -> (ContainsFloats, Vec>) { - let replaced = ReplacedContent::for_element(root_element, context); +// fn construct_for_root_element( +// context: &Context, +// root_element: dom::NodeId, +// style: Arc, +// ) -> (ContainsFloats, Vec>) { +// 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, - }; +// 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)]); - } - } +// 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 })], - ) - } -} +// let contents = IndependentFormattingContext::construct( +// context, +// &style, +// display_inside, +// Contents::OfElement(root_element), +// ); +// if style.box_.position.is_absolutely_positioned() { +// ( +// ContainsFloats::No, +// vec![Arc::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox( +// AbsolutelyPositionedBox { style, contents }, +// ))], +// ) +// } else if style.box_.float.is_floating() { +// ( +// ContainsFloats::Yes, +// vec![Arc::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox { +// contents, +// style, +// }))], +// ) +// } else { +// ( +// ContainsFloats::No, +// vec![Arc::new(BlockLevelBox::Independent { style, contents })], +// ) +// } +// } -impl BoxTreeRoot { - fn layout(&self, viewport: crate::primitives::Size) -> Vec { - let initial_containing_block_size = Vec2 { - inline: Length { px: viewport.width }, - block: Length { - px: viewport.height, - }, - }; +// impl BoxTreeRoot { +// fn layout(&self, viewport: crate::geom::Size) -> Vec { +// let initial_containing_block_size = Vec2 { +// inline: Length { px: viewport.width }, +// block: Length { +// px: viewport.height, +// }, +// }; - let initial_containing_block = ContainingBlock { - inline_size: initial_containing_block_size.inline, - block_size: LengthOrAuto::Length(initial_containing_block_size.block), - // FIXME: use the document’s mode: - // https://drafts.csswg.org/css-writing-modes/#principal-flow - mode: (WritingMode::HorizontalTb, Direction::Ltr), - }; - let dummy_tree_rank = 0; - let mut absolutely_positioned_fragments = vec![]; - let mut flow_children = self.0.layout( - &initial_containing_block, - dummy_tree_rank, - &mut absolutely_positioned_fragments, - ); +// let initial_containing_block = ContainingBlock { +// inline_size: initial_containing_block_size.inline, +// block_size: LengthOrAuto::Length(initial_containing_block_size.block), +// // FIXME: use the document’s mode: +// // https://drafts.csswg.org/css-writing-modes/#principal-flow +// mode: (WritingMode::HorizontalTb, Direction::Ltr), +// }; +// let dummy_tree_rank = 0; +// let mut absolutely_positioned_fragments = vec![]; +// let mut 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, - }; - flow_children.fragments.par_extend( - absolutely_positioned_fragments - .par_iter() - .map(|a| a.layout(&initial_containing_block)), - ); - flow_children.fragments - } -} +// 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 +// } +// } diff --git a/components/layout_2020/fragments.rs b/components/layout_2020/fragments.rs index f2a4dcd2986..ceb2fdf7971 100644 --- a/components/layout_2020/fragments.rs +++ b/components/layout_2020/fragments.rs @@ -1,5 +1,14 @@ -use super::*; -use crate::text::ShapedSegment; +/* 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), @@ -45,7 +54,7 @@ pub(crate) struct AnonymousFragment { pub(crate) struct TextFragment { pub parent_style: Arc, pub content_rect: Rect, - pub text: ShapedSegment, + // pub text: ShapedSegment, } impl AnonymousFragment { diff --git a/components/layout_2020/geom.rs b/components/layout_2020/geom.rs index 2032a3e72ab..af8d37393b5 100644 --- a/components/layout_2020/geom.rs +++ b/components/layout_2020/geom.rs @@ -1,20 +1,31 @@ -pub(crate) use crate::style::values::Length; -use crate::style::values::{LengthOrAuto, LengthOrPercentage, LengthOrPercentageOrAuto}; +/* 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 = euclid::Point2D; +pub type Size = euclid::Size2D; +pub type Rect = euclid::Rect; pub(crate) mod physical { - #[derive(Debug, Clone)] + #[derive(Clone, Debug)] pub(crate) struct Vec2 { pub x: T, pub y: T, } - #[derive(Debug, Clone)] + #[derive(Clone, Debug)] pub(crate) struct Rect { pub top_left: Vec2, pub size: Vec2, } - #[derive(Debug, Clone)] + #[derive(Clone, Debug)] pub(crate) struct Sides { pub top: T, pub left: T, @@ -24,19 +35,19 @@ pub(crate) mod physical { } pub(crate) mod flow_relative { - #[derive(Debug, Clone)] + #[derive(Clone, Debug)] pub(crate) struct Vec2 { pub inline: T, pub block: T, } - #[derive(Debug, Clone)] + #[derive(Clone, Debug)] pub(crate) struct Rect { pub start_corner: Vec2, pub size: Vec2, } - #[derive(Debug, Clone)] + #[derive(Clone, Debug)] pub(crate) struct Sides { pub inline_start: T, pub inline_end: T, @@ -45,9 +56,6 @@ pub(crate) mod flow_relative { } } -use crate::style::values::{Direction, WritingMode}; -use std::ops::{Add, AddAssign, Sub}; - impl Add<&'_ physical::Vec2> for &'_ physical::Vec2 where T: Add + Copy, @@ -145,9 +153,9 @@ impl flow_relative::Vec2 { } } -impl From> for crate::primitives::Point { +impl From> for Point { fn from(v: physical::Vec2) -> Self { - crate::primitives::Point::from_lengths(v.x.into(), v.y.into()) + Point::from_lengths(v.x.into(), v.y.into()) } } @@ -159,18 +167,14 @@ impl physical::Sides { // https://drafts.csswg.org/css-writing-modes/#logical-to-physical let (bs, be) = match mode.0 { HorizontalTb => (&self.top, &self.bottom), - VerticalRl | SidewaysRl => (&self.right, &self.left), - VerticalLr | SidewaysLr => (&self.left, &self.right), + 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) | (SidewaysRl, Ltr) | (VerticalLr, Ltr) | (SidewaysLr, Rtl) => { - (&self.top, &self.bottom) - } - (VerticalRl, Rtl) | (SidewaysRl, Rtl) | (VerticalLr, Rtl) | (SidewaysLr, Ltr) => { - (&self.bottom, &self.top) - } + (VerticalRl, Ltr) | (VerticalLr, Ltr) => (&self.top, &self.bottom), + (VerticalRl, Rtl) | (VerticalLr, Rtl) => (&self.bottom, &self.top), }; flow_relative::Sides { inline_start: is.clone(), @@ -229,13 +233,13 @@ impl flow_relative::Sides { } } -impl flow_relative::Sides { +impl flow_relative::Sides { pub fn percentages_relative_to(&self, basis: Length) -> flow_relative::Sides { self.map(|s| s.percentage_relative_to(basis)) } } -impl flow_relative::Sides { +impl flow_relative::Sides { pub fn percentages_relative_to(&self, basis: Length) -> flow_relative::Sides { self.map(|s| s.percentage_relative_to(basis)) } @@ -320,11 +324,11 @@ impl physical::Rect { } } -impl From> for crate::primitives::Rect { +impl From> for Rect { fn from(r: physical::Rect) -> Self { - crate::primitives::Rect { - origin: crate::primitives::Point::new(r.top_left.x.px, r.top_left.y.px), - size: crate::primitives::Size::new(r.size.x.px, r.size.y.px), + 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()), } } } diff --git a/components/layout_2020/lib.rs b/components/layout_2020/lib.rs index af5709eae38..05a5caa9771 100644 --- a/components/layout_2020/lib.rs +++ b/components/layout_2020/lib.rs @@ -2,11 +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; +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; + +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; + +/// 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, + display_inside: DisplayInside, + contents: Contents>, + ) -> 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>, + ) -> 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>, + ) -> 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, + mode: (WritingMode, Direction), +} + +/// https://drafts.csswg.org/css2/visuren.html#relative-positioning +fn relative_adjustement( + style: &ComputedValues, + inline_size: Length, + block_size: LengthOrAuto, +) -> Vec2 { + 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 it’s stable. +// https://github.com/rust-lang/rust/issues/61129 +fn take(x: &mut T) -> T +where + T: Default, +{ + std::mem::replace(x, Default::default()) +} diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs index 9794d1acdae..55888fb8842 100644 --- a/components/layout_2020/positioned.rs +++ b/components/layout_2020/positioned.rs @@ -1,30 +1,41 @@ -use super::*; -use rayon::prelude::*; +/* 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(super) struct AbsolutelyPositionedBox { +pub(crate) struct AbsolutelyPositionedBox { pub style: Arc, pub contents: IndependentFormattingContext, } #[derive(Debug)] -pub(super) struct AbsolutelyPositionedFragment<'box_> { +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(super) tree_rank: usize, + pub(crate) tree_rank: usize, - pub(super) inline_start: AbsoluteBoxOffsets, - inline_size: LengthOrPercentageOrAuto, + pub(crate) inline_start: AbsoluteBoxOffsets, + inline_size: LengthPercentageOrAuto, - pub(super) block_start: AbsoluteBoxOffsets, - block_size: LengthOrPercentageOrAuto, + pub(crate) block_start: AbsoluteBoxOffsets, + block_size: LengthPercentageOrAuto, } #[derive(Clone, Copy, Debug)] -pub(super) enum AbsoluteBoxOffsets { +pub(crate) enum AbsoluteBoxOffsets { StaticStart { start: Length }, Start { start: NonStatic }, End { end: NonStatic }, @@ -32,7 +43,7 @@ pub(super) enum AbsoluteBoxOffsets { } impl AbsolutelyPositionedBox { - pub(super) fn layout<'a>( + pub(crate) fn layout<'a>( &'a self, initial_start_corner: Vec2, tree_rank: usize, @@ -46,9 +57,9 @@ impl AbsolutelyPositionedBox { fn absolute_box_offsets( initial_static_start: Length, - start: LengthOrPercentageOrAuto, - end: LengthOrPercentageOrAuto, - ) -> AbsoluteBoxOffsets { + start: LengthPercentageOrAuto, + end: LengthPercentageOrAuto, + ) -> AbsoluteBoxOffsets { match (start.non_auto(), end.non_auto()) { (None, None) => AbsoluteBoxOffsets::StaticStart { start: initial_static_start, @@ -82,7 +93,7 @@ impl AbsolutelyPositionedBox { } impl<'a> AbsolutelyPositionedFragment<'a> { - pub(super) fn in_positioned_containing_block( + pub(crate) fn in_positioned_containing_block( absolute: &[Self], fragments: &mut Vec, content_rect_size: &Vec2, @@ -112,13 +123,13 @@ impl<'a> AbsolutelyPositionedFragment<'a> { })) } - pub(super) fn layout(&self, containing_block: &DefiniteContainingBlock) -> Fragment { + 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().percentages_relative_to(cbis); + let border = style.border_width(); let computed_margin = style.margin().percentages_relative_to(cbis); let pb = &padding + &border; @@ -133,8 +144,8 @@ impl<'a> AbsolutelyPositionedFragment<'a> { computed_margin_start: LengthOrAuto, computed_margin_end: LengthOrAuto, solve_margins: impl FnOnce(Length) -> (Length, Length), - box_offsets: AbsoluteBoxOffsets, - size: LengthOrPercentageOrAuto, + box_offsets: AbsoluteBoxOffsets, + size: LengthPercentageOrAuto, ) -> (Anchor, LengthOrAuto, Length, Length) { let size = size.percentage_relative_to(containing_size); match box_offsets { @@ -163,40 +174,42 @@ impl<'a> AbsolutelyPositionedFragment<'a> { let mut margin_start = computed_margin_start.auto_is(Length::zero); let mut margin_end = computed_margin_end.auto_is(Length::zero); - let size = if let LengthOrAuto::Length(size) = size { - use LengthOrAuto::Auto; + let size = if let LengthOrAuto::LengthPercentage(size) = size { let margins = containing_size - start - end - padding_border_sum - size; match (computed_margin_start, computed_margin_end) { - (Auto, Auto) => { + (LengthOrAuto::Auto, LengthOrAuto::Auto) => { let (s, e) = solve_margins(margins); margin_start = s; margin_end = e; - } - (Auto, LengthOrAuto::Length(end)) => { + }, + (LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(end)) => { margin_start = margins - end; - } - (LengthOrAuto::Length(start), Auto) => { + }, + (LengthOrAuto::LengthPercentage(start), LengthOrAuto::Auto) => { margin_end = margins - start; - } - (LengthOrAuto::Length(_), LengthOrAuto::Length(_)) => {} + }, + ( + 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 + containing_size - + start - + end - + padding_border_sum - + margin_start - + margin_end }; ( Anchor::Start(start), - LengthOrAuto::Length(size), + LengthOrAuto::LengthPercentage(size), margin_start, margin_end, ) - } + }, } } @@ -206,7 +219,7 @@ impl<'a> AbsolutelyPositionedFragment<'a> { computed_margin.inline_start, computed_margin.inline_end, |margins| { - if margins.px >= 0. { + if margins.px() >= 0. { (margins / 2., margins / 2.) } else { (Length::zero(), margins) @@ -303,7 +316,7 @@ impl<'a> AbsolutelyPositionedFragment<'a> { } } -pub(super) fn adjust_static_positions( +pub(crate) fn adjust_static_positions( absolutely_positioned_fragments: &mut [AbsolutelyPositionedFragment], child_fragments: &mut [Fragment], tree_rank_in_parent: usize, diff --git a/components/layout_2020/primitives.rs b/components/layout_2020/primitives.rs deleted file mode 100644 index 0991478ac06..00000000000 --- a/components/layout_2020/primitives.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::text; - -/// Origin at top-left corner, unit `1px` -pub struct CssPx; - -pub use euclid::point2 as point; -pub use euclid::rect; -pub type Length = euclid::Length; -pub type Point = euclid::TypedPoint2D; -pub type Size = euclid::TypedSize2D; -pub type Rect = euclid::TypedRect; -pub type SideOffsets = euclid::TypedSideOffsets2D; -pub type Scale = euclid::TypedScale; - -#[derive(Copy, Clone, PartialEq)] -pub struct RGBA(pub f32, pub f32, pub f32, pub f32); - -pub struct TextRun<'a> { - pub segment: &'a text::ShapedSegment, - pub font_size: Length, - pub origin: Point, -} - -impl From for RGBA { - fn from(c: cssparser::RGBA) -> Self { - RGBA(c.red_f32(), c.green_f32(), c.blue_f32(), c.alpha_f32()) - } -} diff --git a/components/layout_2020/replaced.rs b/components/layout_2020/replaced.rs index 312326c84af..04defc26b02 100644 --- a/components/layout_2020/replaced.rs +++ b/components/layout_2020/replaced.rs @@ -1,5 +1,9 @@ -use super::*; -use crate::dom::NodeId; +/* 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 { @@ -7,7 +11,10 @@ pub(super) enum ReplacedContent { } impl ReplacedContent { - pub fn for_element(_element: NodeId, _context: &Context) -> Option { + pub fn for_element<'dom, Node>(element: Node, _context: &SharedStyleContext) -> Option + where + Node: NodeExt<'dom>, + { // FIXME: implement etc. None } diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs new file mode 100644 index 00000000000..bb0b0411dec --- /dev/null +++ b/components/layout_2020/style_ext.rs @@ -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; + fn box_size(&self) -> flow_relative::Vec2; + fn padding(&self) -> flow_relative::Sides; + fn border_width(&self) -> flow_relative::Sides; + fn margin(&self) -> flow_relative::Sides; +} + +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 { + 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 { + 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 { + 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 { + 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 { + 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 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, + } +} diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index 47feb8e1f2a..5855dea84cf 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -565,6 +565,7 @@ impl LayoutThread { traversal_flags: TraversalFlags::empty(), snapshot_map: snapshot_map, }, + font_cache_thread: Mutex::new(self.font_cache_thread.clone()), } } diff --git a/components/style/style_adjuster.rs b/components/style/style_adjuster.rs index 041d2f76caf..577aa1a0267 100644 --- a/components/style/style_adjuster.rs +++ b/components/style/style_adjuster.rs @@ -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() == diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index f43befbbaf0..80b16dcecc9 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -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) -> Option { - self.maybe_to_pixel_length(container_len).map(Au::from) + pub fn maybe_to_used_value(&self, container_len: Option) -> Option { + 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) -> Option { + pub fn maybe_percentage_relative_to(&self, container_len: Option) -> Option { 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) -> 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) -> Option { - 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 for CSSPixelLength { + type Output = Self; + + #[inline] + fn div(self, other: CSSFloat) -> Self { + Self::new(self.px() / other) + } +} + +impl Mul 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 for Au { #[inline] fn from(len: CSSPixelLength) -> Self { @@ -699,6 +782,13 @@ impl From for CSSPixelLength { } } +impl From for euclid::Length { + #[inline] + fn from(length: CSSPixelLength) -> Self { + Self::new(length.0) + } +} + /// An alias of computed `` value. pub type Length = CSSPixelLength; diff --git a/components/style/values/generics/length.rs b/components/style/values/generics/length.rs index 1665f144778..d9cc2396f38 100644 --- a/components/style/values/generics/length.rs +++ b/components/style/values/generics/length.rs @@ -69,6 +69,39 @@ impl LengthPercentageOrAuto { } } +impl LengthPercentageOrAuto +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 { + 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 Zero for LengthPercentageOrAuto { fn zero() -> Self { LengthPercentageOrAuto::LengthPercentage(Zero::zero()) diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index f481b940e05..7030a669397 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -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> { 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,