mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
Make layout_2020 be layout_2013
This commit is contained in:
parent
87e7e3d429
commit
4846d76e82
49 changed files with 34595 additions and 22 deletions
66
Cargo.lock
generated
66
Cargo.lock
generated
|
@ -2331,7 +2331,48 @@ dependencies = [
|
|||
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)",
|
||||
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"canvas_traits 0.0.1",
|
||||
"crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"embedder_traits 0.0.1",
|
||||
"euclid 0.19.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gfx 0.0.1",
|
||||
"gfx_traits 0.0.1",
|
||||
"html5ever 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ipc-channel 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"malloc_size_of 0.0.1",
|
||||
"msg 0.0.1",
|
||||
"net_traits 0.0.1",
|
||||
"num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ordered-float 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"profile_traits 0.0.1",
|
||||
"range 0.0.1",
|
||||
"rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"script_layout_interface 0.0.1",
|
||||
"script_traits 0.0.1",
|
||||
"selectors 0.21.0",
|
||||
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"servo_arc 0.1.1",
|
||||
"servo_atoms 0.0.1",
|
||||
"servo_config 0.0.1",
|
||||
"servo_geometry 0.0.1",
|
||||
"servo_url 0.0.1",
|
||||
"size_of_test 0.0.1",
|
||||
"smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"style 0.0.1",
|
||||
"style_traits 0.0.1",
|
||||
"unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-script 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webrender_api 0.60.0 (git+https://github.com/servo/webrender)",
|
||||
"xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2384,20 +2425,45 @@ dependencies = [
|
|||
name = "layout_thread_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)",
|
||||
"crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"embedder_traits 0.0.1",
|
||||
"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)",
|
||||
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gfx 0.0.1",
|
||||
"gfx_traits 0.0.1",
|
||||
"histogram 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"html5ever 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ipc-channel 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"layout_2020 0.0.1",
|
||||
"layout_traits 0.0.1",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"malloc_size_of 0.0.1",
|
||||
"metrics 0.0.1",
|
||||
"msg 0.0.1",
|
||||
"net_traits 0.0.1",
|
||||
"parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"profile_traits 0.0.1",
|
||||
"range 0.0.1",
|
||||
"rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"script 0.0.1",
|
||||
"script_layout_interface 0.0.1",
|
||||
"script_traits 0.0.1",
|
||||
"selectors 0.21.0",
|
||||
"serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"servo_allocator 0.0.1",
|
||||
"servo_arc 0.1.1",
|
||||
"servo_atoms 0.0.1",
|
||||
"servo_config 0.0.1",
|
||||
"servo_geometry 0.0.1",
|
||||
"servo_url 0.0.1",
|
||||
"style 0.0.1",
|
||||
"style_traits 0.0.1",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webrender_api 0.60.0 (git+https://github.com/servo/webrender)",
|
||||
]
|
||||
|
||||
|
|
|
@ -7,9 +7,53 @@ edition = "2018"
|
|||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "layout"
|
||||
path = "lib.rs"
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
app_units = "0.7"
|
||||
atomic_refcell = "0.1"
|
||||
bitflags = "1.0"
|
||||
canvas_traits = {path = "../canvas_traits"}
|
||||
crossbeam-channel = "0.3"
|
||||
embedder_traits = {path = "../embedder_traits"}
|
||||
euclid = "0.19"
|
||||
fnv = "1.0"
|
||||
fxhash = "0.2"
|
||||
gfx = {path = "../gfx"}
|
||||
gfx_traits = {path = "../gfx_traits"}
|
||||
html5ever = "0.23"
|
||||
ipc-channel = "0.11"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
malloc_size_of = { path = "../malloc_size_of" }
|
||||
msg = {path = "../msg"}
|
||||
net_traits = {path = "../net_traits"}
|
||||
num-traits = "0.2"
|
||||
ordered-float = "1.0"
|
||||
parking_lot = "0.8"
|
||||
profile_traits = {path = "../profile_traits"}
|
||||
range = {path = "../range"}
|
||||
rayon = "1"
|
||||
script_layout_interface = {path = "../script_layout_interface"}
|
||||
script_traits = {path = "../script_traits"}
|
||||
selectors = { path = "../selectors" }
|
||||
serde = "1.0"
|
||||
servo_arc = {path = "../servo_arc"}
|
||||
servo_atoms = {path = "../atoms"}
|
||||
servo_geometry = {path = "../geometry"}
|
||||
serde_json = "1.0"
|
||||
servo_config = {path = "../config"}
|
||||
servo_url = {path = "../url"}
|
||||
smallvec = { version = "0.6", features = ["std", "union"] }
|
||||
style = {path = "../style", features = ["servo", "servo-layout-2020"]}
|
||||
style_traits = {path = "../style_traits"}
|
||||
unicode-bidi = {version = "0.3", features = ["with_serde"]}
|
||||
unicode-script = {version = "0.3", features = ["harfbuzz"]}
|
||||
webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
|
||||
xi-unicode = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
size_of_test = {path = "../size_of_test"}
|
||||
|
|
211
components/layout_2020/animation.rs
Normal file
211
components/layout_2020/animation.rs
Normal file
|
@ -0,0 +1,211 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS transitions and animations.
|
||||
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::items::OpaqueNode;
|
||||
use crate::flow::{Flow, GetBaseFlow};
|
||||
use crate::opaque_node::OpaqueNodeMethods;
|
||||
use crossbeam_channel::Receiver;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use script_traits::UntrustedNodeAddress;
|
||||
use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg};
|
||||
use style::animation::{update_style_for_animation, Animation};
|
||||
use style::dom::TElement;
|
||||
use style::font_metrics::ServoMetricsProvider;
|
||||
use style::selector_parser::RestyleDamage;
|
||||
use style::timer::Timer;
|
||||
|
||||
/// Processes any new animations that were discovered after style recalculation.
|
||||
/// Also expire any old animations that have completed, inserting them into
|
||||
/// `expired_animations`.
|
||||
pub fn update_animation_state<E>(
|
||||
constellation_chan: &IpcSender<ConstellationMsg>,
|
||||
script_chan: &IpcSender<ConstellationControlMsg>,
|
||||
running_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
|
||||
expired_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
|
||||
mut keys_to_remove: FxHashSet<OpaqueNode>,
|
||||
mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>,
|
||||
new_animations_receiver: &Receiver<Animation>,
|
||||
pipeline_id: PipelineId,
|
||||
timer: &Timer,
|
||||
) where
|
||||
E: TElement,
|
||||
{
|
||||
let mut new_running_animations = vec![];
|
||||
while let Ok(animation) = new_animations_receiver.try_recv() {
|
||||
let mut should_push = true;
|
||||
if let Animation::Keyframes(ref node, _, ref name, ref state) = animation {
|
||||
// If the animation was already present in the list for the
|
||||
// node, just update its state, else push the new animation to
|
||||
// run.
|
||||
if let Some(ref mut animations) = running_animations.get_mut(node) {
|
||||
// TODO: This being linear is probably not optimal.
|
||||
for anim in animations.iter_mut() {
|
||||
if let Animation::Keyframes(_, _, ref anim_name, ref mut anim_state) = *anim {
|
||||
if *name == *anim_name {
|
||||
debug!("update_animation_state: Found other animation {}", name);
|
||||
anim_state.update_from_other(&state, timer);
|
||||
should_push = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if should_push {
|
||||
new_running_animations.push(animation);
|
||||
}
|
||||
}
|
||||
|
||||
if running_animations.is_empty() && new_running_animations.is_empty() {
|
||||
// Nothing to do. Return early so we don't flood the compositor with
|
||||
// `ChangeRunningAnimationsState` messages.
|
||||
return;
|
||||
}
|
||||
|
||||
let now = timer.seconds();
|
||||
// Expire old running animations.
|
||||
//
|
||||
// TODO: Do not expunge Keyframes animations, since we need that state if
|
||||
// the animation gets re-triggered. Probably worth splitting in two
|
||||
// different maps, or at least using a linked list?
|
||||
for (key, running_animations) in running_animations.iter_mut() {
|
||||
let mut animations_still_running = vec![];
|
||||
for mut running_animation in running_animations.drain(..) {
|
||||
let still_running = !running_animation.is_expired() &&
|
||||
match running_animation {
|
||||
Animation::Transition(_, started_at, ref frame) => {
|
||||
now < started_at + frame.duration
|
||||
},
|
||||
Animation::Keyframes(_, _, _, ref mut state) => {
|
||||
// This animation is still running, or we need to keep
|
||||
// iterating.
|
||||
now < state.started_at + state.duration || state.tick()
|
||||
},
|
||||
};
|
||||
|
||||
debug!(
|
||||
"update_animation_state({:?}): {:?}",
|
||||
still_running, running_animation
|
||||
);
|
||||
|
||||
if still_running {
|
||||
animations_still_running.push(running_animation);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Animation::Transition(node, _, ref frame) = running_animation {
|
||||
script_chan
|
||||
.send(ConstellationControlMsg::TransitionEnd(
|
||||
node.to_untrusted_node_address(),
|
||||
frame.property_animation.property_name().into(),
|
||||
frame.duration,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
expired_animations
|
||||
.entry(*key)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(running_animation);
|
||||
}
|
||||
|
||||
if animations_still_running.is_empty() {
|
||||
keys_to_remove.insert(*key);
|
||||
} else {
|
||||
*running_animations = animations_still_running
|
||||
}
|
||||
}
|
||||
|
||||
for key in keys_to_remove {
|
||||
running_animations.remove(&key).unwrap();
|
||||
}
|
||||
|
||||
// Add new running animations.
|
||||
for new_running_animation in new_running_animations {
|
||||
if new_running_animation.is_transition() {
|
||||
match newly_transitioning_nodes {
|
||||
Some(ref mut nodes) => {
|
||||
nodes.push(new_running_animation.node().to_untrusted_node_address());
|
||||
},
|
||||
None => {
|
||||
warn!("New transition encountered from compositor-initiated layout.");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
running_animations
|
||||
.entry(*new_running_animation.node())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(new_running_animation)
|
||||
}
|
||||
|
||||
let animation_state = if running_animations.is_empty() {
|
||||
AnimationState::NoAnimationsPresent
|
||||
} else {
|
||||
AnimationState::AnimationsPresent
|
||||
};
|
||||
|
||||
constellation_chan
|
||||
.send(ConstellationMsg::ChangeRunningAnimationsState(
|
||||
pipeline_id,
|
||||
animation_state,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Recalculates style for a set of animations. This does *not* run with the DOM
|
||||
/// lock held. Returns a set of nodes associated with animations that are no longer
|
||||
/// valid.
|
||||
pub fn recalc_style_for_animations<E>(
|
||||
context: &LayoutContext,
|
||||
flow: &mut dyn Flow,
|
||||
animations: &FxHashMap<OpaqueNode, Vec<Animation>>,
|
||||
) -> FxHashSet<OpaqueNode>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
let mut invalid_nodes = animations.keys().cloned().collect();
|
||||
do_recalc_style_for_animations::<E>(context, flow, animations, &mut invalid_nodes);
|
||||
invalid_nodes
|
||||
}
|
||||
|
||||
fn do_recalc_style_for_animations<E>(
|
||||
context: &LayoutContext,
|
||||
flow: &mut dyn Flow,
|
||||
animations: &FxHashMap<OpaqueNode, Vec<Animation>>,
|
||||
invalid_nodes: &mut FxHashSet<OpaqueNode>,
|
||||
) where
|
||||
E: TElement,
|
||||
{
|
||||
let mut damage = RestyleDamage::empty();
|
||||
flow.mutate_fragments(&mut |fragment| {
|
||||
if let Some(ref animations) = animations.get(&fragment.node) {
|
||||
invalid_nodes.remove(&fragment.node);
|
||||
for animation in animations.iter() {
|
||||
let old_style = fragment.style.clone();
|
||||
update_style_for_animation::<E>(
|
||||
&context.style_context,
|
||||
animation,
|
||||
&mut fragment.style,
|
||||
&ServoMetricsProvider,
|
||||
);
|
||||
let difference =
|
||||
RestyleDamage::compute_style_difference(&old_style, &fragment.style);
|
||||
damage |= difference.damage;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let base = flow.mut_base();
|
||||
base.restyle_damage.insert(damage);
|
||||
for kid in base.children.iter_mut() {
|
||||
do_recalc_style_for_animations::<E>(context, kid, animations, invalid_nodes)
|
||||
}
|
||||
}
|
3697
components/layout_2020/block.rs
Normal file
3697
components/layout_2020/block.rs
Normal file
File diff suppressed because it is too large
Load diff
2443
components/layout_2020/construct.rs
Normal file
2443
components/layout_2020/construct.rs
Normal file
File diff suppressed because it is too large
Load diff
201
components/layout_2020/context.rs
Normal file
201
components/layout_2020/context.rs
Normal file
|
@ -0,0 +1,201 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Data needed by the layout thread.
|
||||
|
||||
use crate::display_list::items::{OpaqueNode, WebRenderImageInfo};
|
||||
use crate::opaque_node::OpaqueNodeMethods;
|
||||
use fnv::FnvHasher;
|
||||
use gfx::font_cache_thread::FontCacheThread;
|
||||
use gfx::font_context::FontContext;
|
||||
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use net_traits::image_cache::{CanRequestImages, ImageCache, ImageState};
|
||||
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
|
||||
use parking_lot::RwLock;
|
||||
use script_layout_interface::{PendingImage, PendingImageState};
|
||||
use script_traits::Painter;
|
||||
use script_traits::UntrustedNodeAddress;
|
||||
use servo_atoms::Atom;
|
||||
use servo_url::ServoUrl;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use style::context::RegisteredSpeculativePainter;
|
||||
use style::context::SharedStyleContext;
|
||||
|
||||
pub type LayoutFontContext = FontContext<FontCacheThread>;
|
||||
|
||||
thread_local!(static FONT_CONTEXT_KEY: RefCell<Option<LayoutFontContext>> = RefCell::new(None));
|
||||
|
||||
pub fn with_thread_local_font_context<F, R>(layout_context: &LayoutContext, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut LayoutFontContext) -> R,
|
||||
{
|
||||
FONT_CONTEXT_KEY.with(|k| {
|
||||
let mut font_context = k.borrow_mut();
|
||||
if font_context.is_none() {
|
||||
let font_cache_thread = layout_context.font_cache_thread.lock().unwrap().clone();
|
||||
*font_context = Some(FontContext::new(font_cache_thread));
|
||||
}
|
||||
f(&mut RefMut::map(font_context, |x| x.as_mut().unwrap()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn malloc_size_of_persistent_local_context(ops: &mut MallocSizeOfOps) -> usize {
|
||||
FONT_CONTEXT_KEY.with(|r| {
|
||||
if let Some(ref context) = *r.borrow() {
|
||||
context.size_of(ops)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Layout information shared among all workers. This must be thread-safe.
|
||||
pub struct LayoutContext<'a> {
|
||||
/// The pipeline id of this LayoutContext.
|
||||
pub id: PipelineId,
|
||||
|
||||
/// Bits shared by the layout and style system.
|
||||
pub style_context: SharedStyleContext<'a>,
|
||||
|
||||
/// Reference to the script thread image cache.
|
||||
pub image_cache: Arc<dyn ImageCache>,
|
||||
|
||||
/// Interface to the font cache thread.
|
||||
pub font_cache_thread: Mutex<FontCacheThread>,
|
||||
|
||||
/// A cache of WebRender image info.
|
||||
pub webrender_image_cache: Arc<
|
||||
RwLock<
|
||||
HashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo, BuildHasherDefault<FnvHasher>>,
|
||||
>,
|
||||
>,
|
||||
|
||||
/// Paint worklets
|
||||
pub registered_painters: &'a dyn RegisteredPainters,
|
||||
|
||||
/// A list of in-progress image loads to be shared with the script thread.
|
||||
/// A None value means that this layout was not initiated by the script thread.
|
||||
pub pending_images: Option<Mutex<Vec<PendingImage>>>,
|
||||
|
||||
/// A list of nodes that have just initiated a CSS transition.
|
||||
/// A None value means that this layout was not initiated by the script thread.
|
||||
pub newly_transitioning_nodes: Option<Mutex<Vec<UntrustedNodeAddress>>>,
|
||||
}
|
||||
|
||||
impl<'a> Drop for LayoutContext<'a> {
|
||||
fn drop(&mut self) {
|
||||
if !thread::panicking() {
|
||||
if let Some(ref pending_images) = self.pending_images {
|
||||
assert!(pending_images.lock().unwrap().is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LayoutContext<'a> {
|
||||
#[inline(always)]
|
||||
pub fn shared_context(&self) -> &SharedStyleContext {
|
||||
&self.style_context
|
||||
}
|
||||
|
||||
pub fn get_or_request_image_or_meta(
|
||||
&self,
|
||||
node: OpaqueNode,
|
||||
url: ServoUrl,
|
||||
use_placeholder: UsePlaceholder,
|
||||
) -> Option<ImageOrMetadataAvailable> {
|
||||
//XXXjdm For cases where we do not request an image, we still need to
|
||||
// ensure the node gets another script-initiated reflow or it
|
||||
// won't be requested at all.
|
||||
let can_request = if self.pending_images.is_some() {
|
||||
CanRequestImages::Yes
|
||||
} else {
|
||||
CanRequestImages::No
|
||||
};
|
||||
|
||||
// See if the image is already available
|
||||
let result =
|
||||
self.image_cache
|
||||
.find_image_or_metadata(url.clone(), use_placeholder, can_request);
|
||||
match result {
|
||||
Ok(image_or_metadata) => Some(image_or_metadata),
|
||||
// Image failed to load, so just return nothing
|
||||
Err(ImageState::LoadError) => None,
|
||||
// Not yet requested - request image or metadata from the cache
|
||||
Err(ImageState::NotRequested(id)) => {
|
||||
let image = PendingImage {
|
||||
state: PendingImageState::Unrequested(url),
|
||||
node: node.to_untrusted_node_address(),
|
||||
id: id,
|
||||
};
|
||||
self.pending_images
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(image);
|
||||
None
|
||||
},
|
||||
// Image has been requested, is still pending. Return no image for this paint loop.
|
||||
// When the image loads it will trigger a reflow and/or repaint.
|
||||
Err(ImageState::Pending(id)) => {
|
||||
//XXXjdm if self.pending_images is not available, we should make sure that
|
||||
// this node gets marked dirty again so it gets a script-initiated
|
||||
// reflow that deals with this properly.
|
||||
if let Some(ref pending_images) = self.pending_images {
|
||||
let image = PendingImage {
|
||||
state: PendingImageState::PendingResponse,
|
||||
node: node.to_untrusted_node_address(),
|
||||
id: id,
|
||||
};
|
||||
pending_images.lock().unwrap().push(image);
|
||||
}
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_webrender_image_for_url(
|
||||
&self,
|
||||
node: OpaqueNode,
|
||||
url: ServoUrl,
|
||||
use_placeholder: UsePlaceholder,
|
||||
) -> Option<WebRenderImageInfo> {
|
||||
if let Some(existing_webrender_image) = self
|
||||
.webrender_image_cache
|
||||
.read()
|
||||
.get(&(url.clone(), use_placeholder))
|
||||
{
|
||||
return Some((*existing_webrender_image).clone());
|
||||
}
|
||||
|
||||
match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) {
|
||||
Some(ImageOrMetadataAvailable::ImageAvailable(image, _)) => {
|
||||
let image_info = WebRenderImageInfo::from_image(&*image);
|
||||
if image_info.key.is_none() {
|
||||
Some(image_info)
|
||||
} else {
|
||||
let mut webrender_image_cache = self.webrender_image_cache.write();
|
||||
webrender_image_cache.insert((url, use_placeholder), image_info);
|
||||
Some(image_info)
|
||||
}
|
||||
},
|
||||
None | Some(ImageOrMetadataAvailable::MetadataAvailable(_)) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A registered painter
|
||||
pub trait RegisteredPainter: RegisteredSpeculativePainter + Painter {}
|
||||
|
||||
/// A set of registered painters
|
||||
pub trait RegisteredPainters: Sync {
|
||||
/// Look up a painter
|
||||
fn get(&self, name: &Atom) -> Option<&dyn RegisteredPainter>;
|
||||
}
|
68
components/layout_2020/data.rs
Normal file
68
components/layout_2020/data.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
/* 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::construct::ConstructionResult;
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
use script_layout_interface::StyleData;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct StyleAndLayoutData {
|
||||
/// Data accessed by script_layout_interface. This must be first to allow
|
||||
/// casting between StyleAndLayoutData and StyleData.
|
||||
pub style_data: StyleData,
|
||||
/// The layout data associated with a node.
|
||||
pub layout_data: AtomicRefCell<LayoutData>,
|
||||
}
|
||||
|
||||
impl StyleAndLayoutData {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
style_data: StyleData::new(),
|
||||
layout_data: AtomicRefCell::new(LayoutData::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data that layout associates with a node.
|
||||
#[repr(C)]
|
||||
pub struct LayoutData {
|
||||
/// The current results of flow construction for this node. This is either a
|
||||
/// flow or a `ConstructionItem`. See comments in `construct.rs` for more
|
||||
/// details.
|
||||
pub flow_construction_result: ConstructionResult,
|
||||
|
||||
pub before_flow_construction_result: ConstructionResult,
|
||||
|
||||
pub after_flow_construction_result: ConstructionResult,
|
||||
|
||||
pub details_summary_flow_construction_result: ConstructionResult,
|
||||
|
||||
pub details_content_flow_construction_result: ConstructionResult,
|
||||
|
||||
/// Various flags.
|
||||
pub flags: LayoutDataFlags,
|
||||
}
|
||||
|
||||
impl LayoutData {
|
||||
/// Creates new layout data.
|
||||
pub fn new() -> LayoutData {
|
||||
Self {
|
||||
flow_construction_result: ConstructionResult::None,
|
||||
before_flow_construction_result: ConstructionResult::None,
|
||||
after_flow_construction_result: ConstructionResult::None,
|
||||
details_summary_flow_construction_result: ConstructionResult::None,
|
||||
details_content_flow_construction_result: ConstructionResult::None,
|
||||
flags: LayoutDataFlags::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct LayoutDataFlags: u8 {
|
||||
#[doc = "Whether a flow has been newly constructed."]
|
||||
const HAS_NEWLY_CONSTRUCTED_FLOW = 0x01;
|
||||
#[doc = "Whether this node has been traversed by layout."]
|
||||
const HAS_BEEN_TRAVERSED = 0x02;
|
||||
}
|
||||
}
|
336
components/layout_2020/display_list/background.rs
Normal file
336
components/layout_2020/display_list/background.rs
Normal file
|
@ -0,0 +1,336 @@
|
|||
/* 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::display_list::border;
|
||||
use app_units::Au;
|
||||
use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
|
||||
use style::computed_values::background_attachment::single_value::T as BackgroundAttachment;
|
||||
use style::computed_values::background_clip::single_value::T as BackgroundClip;
|
||||
use style::computed_values::background_origin::single_value::T as BackgroundOrigin;
|
||||
use style::properties::style_structs::Background;
|
||||
use style::values::computed::{BackgroundSize, NonNegativeLengthPercentageOrAuto};
|
||||
use style::values::specified::background::BackgroundRepeatKeyword;
|
||||
use webrender_api::BorderRadius;
|
||||
|
||||
/// Placment information for both image and gradient backgrounds.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BackgroundPlacement {
|
||||
/// Rendering bounds. The background will start in the uppper-left corner
|
||||
/// and fill the whole area.
|
||||
pub bounds: Rect<Au>,
|
||||
/// Background tile size. Some backgrounds are repeated. These are the
|
||||
/// dimensions of a single image of the background.
|
||||
pub tile_size: Size2D<Au>,
|
||||
/// Spacing between tiles. Some backgrounds are not repeated seamless
|
||||
/// but have seams between them like tiles in real life.
|
||||
pub tile_spacing: Size2D<Au>,
|
||||
/// A clip area. While the background is rendered according to all the
|
||||
/// measures above it is only shown within these bounds.
|
||||
pub clip_rect: Rect<Au>,
|
||||
/// Rounded corners for the clip_rect.
|
||||
pub clip_radii: BorderRadius,
|
||||
/// Whether or not the background is fixed to the viewport.
|
||||
pub fixed: bool,
|
||||
}
|
||||
|
||||
/// Access element at index modulo the array length.
|
||||
///
|
||||
/// Obviously it does not work with empty arrays.
|
||||
///
|
||||
/// This is used for multiple layered background images.
|
||||
/// See: https://drafts.csswg.org/css-backgrounds-3/#layering
|
||||
pub fn get_cyclic<T>(arr: &[T], index: usize) -> &T {
|
||||
&arr[index % arr.len()]
|
||||
}
|
||||
|
||||
/// For a given area and an image compute how big the
|
||||
/// image should be displayed on the background.
|
||||
fn compute_background_image_size(
|
||||
bg_size: BackgroundSize,
|
||||
bounds_size: Size2D<Au>,
|
||||
intrinsic_size: Option<Size2D<Au>>,
|
||||
) -> Size2D<Au> {
|
||||
match intrinsic_size {
|
||||
None => match bg_size {
|
||||
BackgroundSize::Cover | BackgroundSize::Contain => bounds_size,
|
||||
BackgroundSize::ExplicitSize { width, height } => Size2D::new(
|
||||
width
|
||||
.to_used_value(bounds_size.width)
|
||||
.unwrap_or(bounds_size.width),
|
||||
height
|
||||
.to_used_value(bounds_size.height)
|
||||
.unwrap_or(bounds_size.height),
|
||||
),
|
||||
},
|
||||
Some(own_size) => {
|
||||
// If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is
|
||||
// wide.
|
||||
let image_aspect_ratio = own_size.width.to_f32_px() / own_size.height.to_f32_px();
|
||||
let bounds_aspect_ratio =
|
||||
bounds_size.width.to_f32_px() / bounds_size.height.to_f32_px();
|
||||
match (bg_size, image_aspect_ratio < bounds_aspect_ratio) {
|
||||
(BackgroundSize::Contain, false) | (BackgroundSize::Cover, true) => Size2D::new(
|
||||
bounds_size.width,
|
||||
bounds_size.width.scale_by(image_aspect_ratio.recip()),
|
||||
),
|
||||
(BackgroundSize::Contain, true) | (BackgroundSize::Cover, false) => Size2D::new(
|
||||
bounds_size.height.scale_by(image_aspect_ratio),
|
||||
bounds_size.height,
|
||||
),
|
||||
(
|
||||
BackgroundSize::ExplicitSize {
|
||||
width,
|
||||
height: NonNegativeLengthPercentageOrAuto::Auto,
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
let width = width
|
||||
.to_used_value(bounds_size.width)
|
||||
.unwrap_or(own_size.width);
|
||||
Size2D::new(width, width.scale_by(image_aspect_ratio.recip()))
|
||||
},
|
||||
(
|
||||
BackgroundSize::ExplicitSize {
|
||||
width: NonNegativeLengthPercentageOrAuto::Auto,
|
||||
height,
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
let height = height
|
||||
.to_used_value(bounds_size.height)
|
||||
.unwrap_or(own_size.height);
|
||||
Size2D::new(height.scale_by(image_aspect_ratio), height)
|
||||
},
|
||||
(BackgroundSize::ExplicitSize { width, height }, _) => Size2D::new(
|
||||
width
|
||||
.to_used_value(bounds_size.width)
|
||||
.unwrap_or(own_size.width),
|
||||
height
|
||||
.to_used_value(bounds_size.height)
|
||||
.unwrap_or(own_size.height),
|
||||
),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute a rounded clip rect for the background.
|
||||
pub fn clip(
|
||||
bg_clip: BackgroundClip,
|
||||
absolute_bounds: Rect<Au>,
|
||||
border: SideOffsets2D<Au>,
|
||||
border_padding: SideOffsets2D<Au>,
|
||||
border_radii: BorderRadius,
|
||||
) -> (Rect<Au>, BorderRadius) {
|
||||
match bg_clip {
|
||||
BackgroundClip::BorderBox => (absolute_bounds, border_radii),
|
||||
BackgroundClip::PaddingBox => (
|
||||
absolute_bounds.inner_rect(border),
|
||||
border::inner_radii(border_radii, border),
|
||||
),
|
||||
BackgroundClip::ContentBox => (
|
||||
absolute_bounds.inner_rect(border_padding),
|
||||
border::inner_radii(border_radii, border_padding),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines where to place an element background image or gradient.
|
||||
///
|
||||
/// Photos have their resolution as intrinsic size while gradients have
|
||||
/// no intrinsic size.
|
||||
pub fn placement(
|
||||
bg: &Background,
|
||||
viewport_size: Size2D<Au>,
|
||||
absolute_bounds: Rect<Au>,
|
||||
intrinsic_size: Option<Size2D<Au>>,
|
||||
border: SideOffsets2D<Au>,
|
||||
border_padding: SideOffsets2D<Au>,
|
||||
border_radii: BorderRadius,
|
||||
index: usize,
|
||||
) -> BackgroundPlacement {
|
||||
let bg_attachment = *get_cyclic(&bg.background_attachment.0, index);
|
||||
let bg_clip = *get_cyclic(&bg.background_clip.0, index);
|
||||
let bg_origin = *get_cyclic(&bg.background_origin.0, index);
|
||||
let bg_position_x = get_cyclic(&bg.background_position_x.0, index);
|
||||
let bg_position_y = get_cyclic(&bg.background_position_y.0, index);
|
||||
let bg_repeat = get_cyclic(&bg.background_repeat.0, index);
|
||||
let bg_size = *get_cyclic(&bg.background_size.0, index);
|
||||
|
||||
let (clip_rect, clip_radii) = clip(
|
||||
bg_clip,
|
||||
absolute_bounds,
|
||||
border,
|
||||
border_padding,
|
||||
border_radii,
|
||||
);
|
||||
|
||||
let mut fixed = false;
|
||||
let mut bounds = match bg_attachment {
|
||||
BackgroundAttachment::Scroll => match bg_origin {
|
||||
BackgroundOrigin::BorderBox => absolute_bounds,
|
||||
BackgroundOrigin::PaddingBox => absolute_bounds.inner_rect(border),
|
||||
BackgroundOrigin::ContentBox => absolute_bounds.inner_rect(border_padding),
|
||||
},
|
||||
BackgroundAttachment::Fixed => {
|
||||
fixed = true;
|
||||
Rect::new(Point2D::origin(), viewport_size)
|
||||
},
|
||||
};
|
||||
|
||||
let mut tile_size = compute_background_image_size(bg_size, bounds.size, intrinsic_size);
|
||||
|
||||
let mut tile_spacing = Size2D::zero();
|
||||
let own_position = bounds.size - tile_size;
|
||||
let pos_x = bg_position_x.to_used_value(own_position.width);
|
||||
let pos_y = bg_position_y.to_used_value(own_position.height);
|
||||
tile_image_axis(
|
||||
bg_repeat.0,
|
||||
&mut bounds.origin.x,
|
||||
&mut bounds.size.width,
|
||||
&mut tile_size.width,
|
||||
&mut tile_spacing.width,
|
||||
pos_x,
|
||||
clip_rect.origin.x,
|
||||
clip_rect.size.width,
|
||||
);
|
||||
tile_image_axis(
|
||||
bg_repeat.1,
|
||||
&mut bounds.origin.y,
|
||||
&mut bounds.size.height,
|
||||
&mut tile_size.height,
|
||||
&mut tile_spacing.height,
|
||||
pos_y,
|
||||
clip_rect.origin.y,
|
||||
clip_rect.size.height,
|
||||
);
|
||||
|
||||
BackgroundPlacement {
|
||||
bounds,
|
||||
tile_size,
|
||||
tile_spacing,
|
||||
clip_rect,
|
||||
clip_radii,
|
||||
fixed,
|
||||
}
|
||||
}
|
||||
|
||||
fn tile_image_round(
|
||||
position: &mut Au,
|
||||
size: &mut Au,
|
||||
absolute_anchor_origin: Au,
|
||||
image_size: &mut Au,
|
||||
) {
|
||||
if *size == Au(0) || *image_size == Au(0) {
|
||||
*position = Au(0);
|
||||
*size = Au(0);
|
||||
return;
|
||||
}
|
||||
|
||||
let number_of_tiles = (size.to_f32_px() / image_size.to_f32_px()).round().max(1.0);
|
||||
*image_size = *size / (number_of_tiles as i32);
|
||||
tile_image(position, size, absolute_anchor_origin, *image_size);
|
||||
}
|
||||
|
||||
fn tile_image_spaced(
|
||||
position: &mut Au,
|
||||
size: &mut Au,
|
||||
tile_spacing: &mut Au,
|
||||
absolute_anchor_origin: Au,
|
||||
image_size: Au,
|
||||
) {
|
||||
if *size == Au(0) || image_size == Au(0) {
|
||||
*position = Au(0);
|
||||
*size = Au(0);
|
||||
*tile_spacing = Au(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Per the spec, if the space available is not enough for two images, just tile as
|
||||
// normal but only display a single tile.
|
||||
if image_size * 2 >= *size {
|
||||
tile_image(position, size, absolute_anchor_origin, image_size);
|
||||
*tile_spacing = Au(0);
|
||||
*size = image_size;
|
||||
return;
|
||||
}
|
||||
|
||||
// Take the box size, remove room for two tiles on the edges, and then calculate how many
|
||||
// other tiles fit in between them.
|
||||
let size_remaining = *size - (image_size * 2);
|
||||
let num_middle_tiles = (size_remaining.to_f32_px() / image_size.to_f32_px()).floor() as i32;
|
||||
|
||||
// Allocate the remaining space as padding between tiles. background-position is ignored
|
||||
// as per the spec, so the position is just the box origin. We are also ignoring
|
||||
// background-attachment here, which seems unspecced when combined with
|
||||
// background-repeat: space.
|
||||
let space_for_middle_tiles = image_size * num_middle_tiles;
|
||||
*tile_spacing = (size_remaining - space_for_middle_tiles) / (num_middle_tiles + 1);
|
||||
}
|
||||
|
||||
/// Tile an image
|
||||
fn tile_image(position: &mut Au, size: &mut Au, absolute_anchor_origin: Au, image_size: Au) {
|
||||
// Avoid division by zero below!
|
||||
// Images with a zero width or height are not displayed.
|
||||
// Therefore the positions do not matter and can be left unchanged.
|
||||
// NOTE: A possible optimization is not to build
|
||||
// display items in this case at all.
|
||||
if image_size == Au(0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let delta_pixels = absolute_anchor_origin - *position;
|
||||
let image_size_px = image_size.to_f32_px();
|
||||
let tile_count = ((delta_pixels.to_f32_px() + image_size_px - 1.0) / image_size_px).floor();
|
||||
let offset = image_size * (tile_count as i32);
|
||||
let new_position = absolute_anchor_origin - offset;
|
||||
*size = *position - new_position + *size;
|
||||
*position = new_position;
|
||||
}
|
||||
|
||||
/// For either the x or the y axis ajust various values to account for tiling.
|
||||
///
|
||||
/// This is done separately for both axes because the repeat keywords may differ.
|
||||
fn tile_image_axis(
|
||||
repeat: BackgroundRepeatKeyword,
|
||||
position: &mut Au,
|
||||
size: &mut Au,
|
||||
tile_size: &mut Au,
|
||||
tile_spacing: &mut Au,
|
||||
offset: Au,
|
||||
clip_origin: Au,
|
||||
clip_size: Au,
|
||||
) {
|
||||
let absolute_anchor_origin = *position + offset;
|
||||
match repeat {
|
||||
BackgroundRepeatKeyword::NoRepeat => {
|
||||
*position += offset;
|
||||
*size = *tile_size;
|
||||
},
|
||||
BackgroundRepeatKeyword::Repeat => {
|
||||
*position = clip_origin;
|
||||
*size = clip_size;
|
||||
tile_image(position, size, absolute_anchor_origin, *tile_size);
|
||||
},
|
||||
BackgroundRepeatKeyword::Space => {
|
||||
tile_image_spaced(
|
||||
position,
|
||||
size,
|
||||
tile_spacing,
|
||||
absolute_anchor_origin,
|
||||
*tile_size,
|
||||
);
|
||||
let combined_tile_size = *tile_size + *tile_spacing;
|
||||
*position = clip_origin;
|
||||
*size = clip_size;
|
||||
tile_image(position, size, absolute_anchor_origin, combined_tile_size);
|
||||
},
|
||||
BackgroundRepeatKeyword::Round => {
|
||||
tile_image_round(position, size, absolute_anchor_origin, tile_size);
|
||||
*position = clip_origin;
|
||||
*size = clip_size;
|
||||
tile_image(position, size, absolute_anchor_origin, *tile_size);
|
||||
},
|
||||
}
|
||||
}
|
199
components/layout_2020/display_list/border.rs
Normal file
199
components/layout_2020/display_list/border.rs
Normal file
|
@ -0,0 +1,199 @@
|
|||
/* 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::display_list::ToLayout;
|
||||
use app_units::Au;
|
||||
use euclid::{Rect, SideOffsets2D, Size2D};
|
||||
use style::computed_values::border_image_outset::T as BorderImageOutset;
|
||||
use style::properties::style_structs::Border;
|
||||
use style::values::computed::NumberOrPercentage;
|
||||
use style::values::computed::{BorderCornerRadius, BorderImageWidth};
|
||||
use style::values::computed::{BorderImageSideWidth, NonNegativeLengthOrNumber};
|
||||
use style::values::generics::rect::Rect as StyleRect;
|
||||
use style::values::generics::NonNegative;
|
||||
use webrender_api::units::{LayoutSideOffsets, LayoutSize};
|
||||
use webrender_api::{BorderRadius, BorderSide, BorderStyle, ColorF, NormalBorder};
|
||||
|
||||
/// Computes a border radius size against the containing size.
|
||||
///
|
||||
/// Note that percentages in `border-radius` are resolved against the relevant
|
||||
/// box dimension instead of only against the width per [1]:
|
||||
///
|
||||
/// > Percentages: Refer to corresponding dimension of the border box.
|
||||
///
|
||||
/// [1]: https://drafts.csswg.org/css-backgrounds-3/#border-radius
|
||||
fn corner_radius(radius: BorderCornerRadius, containing_size: Size2D<Au>) -> Size2D<Au> {
|
||||
let w = radius.0.width().to_used_value(containing_size.width);
|
||||
let h = radius.0.height().to_used_value(containing_size.height);
|
||||
Size2D::new(w, h)
|
||||
}
|
||||
|
||||
fn scaled_radii(radii: BorderRadius, factor: f32) -> BorderRadius {
|
||||
BorderRadius {
|
||||
top_left: radii.top_left * factor,
|
||||
top_right: radii.top_right * factor,
|
||||
bottom_left: radii.bottom_left * factor,
|
||||
bottom_right: radii.bottom_right * factor,
|
||||
}
|
||||
}
|
||||
|
||||
fn overlapping_radii(size: LayoutSize, radii: BorderRadius) -> BorderRadius {
|
||||
// No two corners' border radii may add up to more than the length of the edge
|
||||
// between them. To prevent that, all radii are scaled down uniformly.
|
||||
fn scale_factor(radius_a: f32, radius_b: f32, edge_length: f32) -> f32 {
|
||||
let required = radius_a + radius_b;
|
||||
|
||||
if required <= edge_length {
|
||||
1.0
|
||||
} else {
|
||||
edge_length / required
|
||||
}
|
||||
}
|
||||
|
||||
let top_factor = scale_factor(radii.top_left.width, radii.top_right.width, size.width);
|
||||
let bottom_factor = scale_factor(
|
||||
radii.bottom_left.width,
|
||||
radii.bottom_right.width,
|
||||
size.width,
|
||||
);
|
||||
let left_factor = scale_factor(radii.top_left.height, radii.bottom_left.height, size.height);
|
||||
let right_factor = scale_factor(
|
||||
radii.top_right.height,
|
||||
radii.bottom_right.height,
|
||||
size.height,
|
||||
);
|
||||
let min_factor = top_factor
|
||||
.min(bottom_factor)
|
||||
.min(left_factor)
|
||||
.min(right_factor);
|
||||
if min_factor < 1.0 {
|
||||
scaled_radii(radii, min_factor)
|
||||
} else {
|
||||
radii
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the four corner radii of a border.
|
||||
///
|
||||
/// Radii may either be absolute or relative to the absolute bounds.
|
||||
/// Each corner radius has a width and a height which may differ.
|
||||
/// Lastly overlapping radii are shrank so they don't collide anymore.
|
||||
pub fn radii(abs_bounds: Rect<Au>, border_style: &Border) -> BorderRadius {
|
||||
// TODO(cgaebel): Support border radii even in the case of multiple border widths.
|
||||
// This is an extension of supporting elliptical radii. For now, all percentage
|
||||
// radii will be relative to the width.
|
||||
|
||||
overlapping_radii(
|
||||
abs_bounds.size.to_layout(),
|
||||
BorderRadius {
|
||||
top_left: corner_radius(border_style.border_top_left_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
top_right: corner_radius(border_style.border_top_right_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
bottom_right: corner_radius(border_style.border_bottom_right_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
bottom_left: corner_radius(border_style.border_bottom_left_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculates radii for the inner side.
|
||||
///
|
||||
/// Radii usually describe the outer side of a border but for the lines to look nice
|
||||
/// the inner radii need to be smaller depending on the line width.
|
||||
///
|
||||
/// This is used to determine clipping areas.
|
||||
pub fn inner_radii(mut radii: BorderRadius, offsets: SideOffsets2D<Au>) -> BorderRadius {
|
||||
fn inner_length(x: f32, offset: Au) -> f32 {
|
||||
0.0_f32.max(x - offset.to_f32_px())
|
||||
}
|
||||
radii.top_left.width = inner_length(radii.top_left.width, offsets.left);
|
||||
radii.bottom_left.width = inner_length(radii.bottom_left.width, offsets.left);
|
||||
|
||||
radii.top_right.width = inner_length(radii.top_right.width, offsets.right);
|
||||
radii.bottom_right.width = inner_length(radii.bottom_right.width, offsets.right);
|
||||
|
||||
radii.top_left.height = inner_length(radii.top_left.height, offsets.top);
|
||||
radii.top_right.height = inner_length(radii.top_right.height, offsets.top);
|
||||
|
||||
radii.bottom_left.height = inner_length(radii.bottom_left.height, offsets.bottom);
|
||||
radii.bottom_right.height = inner_length(radii.bottom_right.height, offsets.bottom);
|
||||
radii
|
||||
}
|
||||
|
||||
/// Creates a four-sided border with square corners and uniform color and width.
|
||||
pub fn simple(color: ColorF, style: BorderStyle) -> NormalBorder {
|
||||
let side = BorderSide { color, style };
|
||||
NormalBorder {
|
||||
left: side,
|
||||
right: side,
|
||||
top: side,
|
||||
bottom: side,
|
||||
radius: BorderRadius::zero(),
|
||||
do_aa: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn side_image_outset(outset: NonNegativeLengthOrNumber, border_width: Au) -> Au {
|
||||
match outset {
|
||||
NonNegativeLengthOrNumber::Length(length) => length.into(),
|
||||
NonNegativeLengthOrNumber::Number(factor) => border_width.scale_by(factor.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the additional border-image area.
|
||||
pub fn image_outset(outset: BorderImageOutset, border: SideOffsets2D<Au>) -> SideOffsets2D<Au> {
|
||||
SideOffsets2D::new(
|
||||
side_image_outset(outset.0, border.top),
|
||||
side_image_outset(outset.1, border.right),
|
||||
side_image_outset(outset.2, border.bottom),
|
||||
side_image_outset(outset.3, border.left),
|
||||
)
|
||||
}
|
||||
|
||||
fn side_image_width(
|
||||
border_image_width: BorderImageSideWidth,
|
||||
border_width: f32,
|
||||
total_length: Au,
|
||||
) -> f32 {
|
||||
match border_image_width {
|
||||
BorderImageSideWidth::LengthPercentage(v) => v.to_used_value(total_length).to_f32_px(),
|
||||
BorderImageSideWidth::Number(x) => border_width * x.0,
|
||||
BorderImageSideWidth::Auto => border_width,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_width(
|
||||
width: &BorderImageWidth,
|
||||
border: LayoutSideOffsets,
|
||||
border_area: Size2D<Au>,
|
||||
) -> LayoutSideOffsets {
|
||||
LayoutSideOffsets::new(
|
||||
side_image_width(width.0, border.top, border_area.height),
|
||||
side_image_width(width.1, border.right, border_area.width),
|
||||
side_image_width(width.2, border.bottom, border_area.height),
|
||||
side_image_width(width.3, border.left, border_area.width),
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
|
||||
match value.0 {
|
||||
NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
|
||||
NumberOrPercentage::Number(n) => n.round() as i32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_slice(
|
||||
border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) -> SideOffsets2D<i32> {
|
||||
SideOffsets2D::new(
|
||||
resolve_percentage(border_image_slice.0, height),
|
||||
resolve_percentage(border_image_slice.1, width),
|
||||
resolve_percentage(border_image_slice.2, height),
|
||||
resolve_percentage(border_image_slice.3, width),
|
||||
)
|
||||
}
|
3024
components/layout_2020/display_list/builder.rs
Normal file
3024
components/layout_2020/display_list/builder.rs
Normal file
File diff suppressed because it is too large
Load diff
167
components/layout_2020/display_list/conversions.rs
Normal file
167
components/layout_2020/display_list/conversions.rs
Normal file
|
@ -0,0 +1,167 @@
|
|||
/* 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 app_units::Au;
|
||||
use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D};
|
||||
use style::computed_values::image_rendering::T as ImageRendering;
|
||||
use style::computed_values::mix_blend_mode::T as MixBlendMode;
|
||||
use style::computed_values::transform_style::T as TransformStyle;
|
||||
use style::values::computed::{BorderStyle, Filter};
|
||||
use style::values::specified::border::BorderImageRepeatKeyword;
|
||||
use style::values::RGBA;
|
||||
use webrender_api as wr;
|
||||
|
||||
pub trait ToLayout {
|
||||
type Type;
|
||||
fn to_layout(&self) -> Self::Type;
|
||||
}
|
||||
|
||||
impl ToLayout for BorderStyle {
|
||||
type Type = wr::BorderStyle;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
BorderStyle::None => wr::BorderStyle::None,
|
||||
BorderStyle::Solid => wr::BorderStyle::Solid,
|
||||
BorderStyle::Double => wr::BorderStyle::Double,
|
||||
BorderStyle::Dotted => wr::BorderStyle::Dotted,
|
||||
BorderStyle::Dashed => wr::BorderStyle::Dashed,
|
||||
BorderStyle::Hidden => wr::BorderStyle::Hidden,
|
||||
BorderStyle::Groove => wr::BorderStyle::Groove,
|
||||
BorderStyle::Ridge => wr::BorderStyle::Ridge,
|
||||
BorderStyle::Inset => wr::BorderStyle::Inset,
|
||||
BorderStyle::Outset => wr::BorderStyle::Outset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Filter {
|
||||
type Type = wr::FilterOp;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
Filter::Blur(radius) => wr::FilterOp::Blur(radius.px()),
|
||||
Filter::Brightness(amount) => wr::FilterOp::Brightness(amount.0),
|
||||
Filter::Contrast(amount) => wr::FilterOp::Contrast(amount.0),
|
||||
Filter::Grayscale(amount) => wr::FilterOp::Grayscale(amount.0),
|
||||
Filter::HueRotate(angle) => wr::FilterOp::HueRotate(angle.radians()),
|
||||
Filter::Invert(amount) => wr::FilterOp::Invert(amount.0),
|
||||
Filter::Opacity(amount) => wr::FilterOp::Opacity(amount.0.into(), amount.0),
|
||||
Filter::Saturate(amount) => wr::FilterOp::Saturate(amount.0),
|
||||
Filter::Sepia(amount) => wr::FilterOp::Sepia(amount.0),
|
||||
// Statically check that DropShadow is impossible.
|
||||
Filter::DropShadow(ref shadow) => match *shadow {},
|
||||
// Statically check that Url is impossible.
|
||||
Filter::Url(ref url) => match *url {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for ImageRendering {
|
||||
type Type = wr::ImageRendering;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
ImageRendering::Auto => wr::ImageRendering::Auto,
|
||||
ImageRendering::CrispEdges => wr::ImageRendering::CrispEdges,
|
||||
ImageRendering::Pixelated => wr::ImageRendering::Pixelated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for MixBlendMode {
|
||||
type Type = wr::MixBlendMode;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
MixBlendMode::Normal => wr::MixBlendMode::Normal,
|
||||
MixBlendMode::Multiply => wr::MixBlendMode::Multiply,
|
||||
MixBlendMode::Screen => wr::MixBlendMode::Screen,
|
||||
MixBlendMode::Overlay => wr::MixBlendMode::Overlay,
|
||||
MixBlendMode::Darken => wr::MixBlendMode::Darken,
|
||||
MixBlendMode::Lighten => wr::MixBlendMode::Lighten,
|
||||
MixBlendMode::ColorDodge => wr::MixBlendMode::ColorDodge,
|
||||
MixBlendMode::ColorBurn => wr::MixBlendMode::ColorBurn,
|
||||
MixBlendMode::HardLight => wr::MixBlendMode::HardLight,
|
||||
MixBlendMode::SoftLight => wr::MixBlendMode::SoftLight,
|
||||
MixBlendMode::Difference => wr::MixBlendMode::Difference,
|
||||
MixBlendMode::Exclusion => wr::MixBlendMode::Exclusion,
|
||||
MixBlendMode::Hue => wr::MixBlendMode::Hue,
|
||||
MixBlendMode::Saturation => wr::MixBlendMode::Saturation,
|
||||
MixBlendMode::Color => wr::MixBlendMode::Color,
|
||||
MixBlendMode::Luminosity => wr::MixBlendMode::Luminosity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for TransformStyle {
|
||||
type Type = wr::TransformStyle;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
TransformStyle::Auto | TransformStyle::Flat => wr::TransformStyle::Flat,
|
||||
TransformStyle::Preserve3d => wr::TransformStyle::Preserve3D,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for RGBA {
|
||||
type Type = wr::ColorF;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::ColorF::new(
|
||||
self.red_f32(),
|
||||
self.green_f32(),
|
||||
self.blue_f32(),
|
||||
self.alpha_f32(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Point2D<Au> {
|
||||
type Type = wr::units::LayoutPoint;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutPoint::new(self.x.to_f32_px(), self.y.to_f32_px())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Rect<Au> {
|
||||
type Type = wr::units::LayoutRect;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutRect::new(self.origin.to_layout(), self.size.to_layout())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for SideOffsets2D<Au> {
|
||||
type Type = wr::units::LayoutSideOffsets;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutSideOffsets::new(
|
||||
self.top.to_f32_px(),
|
||||
self.right.to_f32_px(),
|
||||
self.bottom.to_f32_px(),
|
||||
self.left.to_f32_px(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Size2D<Au> {
|
||||
type Type = wr::units::LayoutSize;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutSize::new(self.width.to_f32_px(), self.height.to_f32_px())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Vector2D<Au> {
|
||||
type Type = wr::units::LayoutVector2D;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutVector2D::new(self.x.to_f32_px(), self.y.to_f32_px())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for BorderImageRepeatKeyword {
|
||||
type Type = wr::RepeatMode;
|
||||
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
BorderImageRepeatKeyword::Stretch => wr::RepeatMode::Stretch,
|
||||
BorderImageRepeatKeyword::Repeat => wr::RepeatMode::Repeat,
|
||||
BorderImageRepeatKeyword::Round => wr::RepeatMode::Round,
|
||||
BorderImageRepeatKeyword::Space => wr::RepeatMode::Space,
|
||||
}
|
||||
}
|
||||
}
|
323
components/layout_2020/display_list/gradient.rs
Normal file
323
components/layout_2020/display_list/gradient.rs
Normal file
|
@ -0,0 +1,323 @@
|
|||
/* 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::display_list::ToLayout;
|
||||
use app_units::Au;
|
||||
use euclid::{Point2D, Size2D, Vector2D};
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::image::{EndingShape, LineDirection};
|
||||
use style::values::computed::{Angle, GradientItem, LengthPercentage, Percentage, Position};
|
||||
use style::values::generics::image::{Circle, ColorStop, Ellipse, ShapeExtent};
|
||||
use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient};
|
||||
|
||||
/// A helper data structure for gradients.
|
||||
#[derive(Clone, Copy)]
|
||||
struct StopRun {
|
||||
start_offset: f32,
|
||||
end_offset: f32,
|
||||
start_index: usize,
|
||||
stop_count: usize,
|
||||
}
|
||||
|
||||
/// Determines the radius of a circle if it was not explictly provided.
|
||||
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
|
||||
fn circle_size_keyword(
|
||||
keyword: ShapeExtent,
|
||||
size: &Size2D<Au>,
|
||||
center: &Point2D<Au>,
|
||||
) -> Size2D<Au> {
|
||||
let radius = match keyword {
|
||||
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
||||
let dist = distance_to_sides(size, center, ::std::cmp::min);
|
||||
::std::cmp::min(dist.width, dist.height)
|
||||
},
|
||||
ShapeExtent::FarthestSide => {
|
||||
let dist = distance_to_sides(size, center, ::std::cmp::max);
|
||||
::std::cmp::max(dist.width, dist.height)
|
||||
},
|
||||
ShapeExtent::ClosestCorner => distance_to_corner(size, center, ::std::cmp::min),
|
||||
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
||||
distance_to_corner(size, center, ::std::cmp::max)
|
||||
},
|
||||
};
|
||||
Size2D::new(radius, radius)
|
||||
}
|
||||
|
||||
/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
|
||||
fn ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
||||
where
|
||||
F: Fn(Au, Au) -> Au,
|
||||
{
|
||||
let dist = distance_to_sides(size, center, cmp);
|
||||
Size2D::new(
|
||||
dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
|
||||
dist.height
|
||||
.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
|
||||
)
|
||||
}
|
||||
|
||||
/// Determines the radius of an ellipse if it was not explictly provided.
|
||||
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
|
||||
fn ellipse_size_keyword(
|
||||
keyword: ShapeExtent,
|
||||
size: &Size2D<Au>,
|
||||
center: &Point2D<Au>,
|
||||
) -> Size2D<Au> {
|
||||
match keyword {
|
||||
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
||||
distance_to_sides(size, center, ::std::cmp::min)
|
||||
},
|
||||
ShapeExtent::FarthestSide => distance_to_sides(size, center, ::std::cmp::max),
|
||||
ShapeExtent::ClosestCorner => ellipse_radius(size, center, ::std::cmp::min),
|
||||
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
||||
ellipse_radius(size, center, ::std::cmp::max)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_gradient_stops(
|
||||
style: &ComputedValues,
|
||||
gradient_items: &[GradientItem],
|
||||
total_length: Au,
|
||||
) -> GradientBuilder {
|
||||
// Determine the position of each stop per CSS-IMAGES § 3.4.
|
||||
|
||||
// Only keep the color stops, discard the color interpolation hints.
|
||||
let mut stop_items = gradient_items
|
||||
.iter()
|
||||
.filter_map(|item| match *item {
|
||||
GradientItem::SimpleColorStop(color) => Some(ColorStop {
|
||||
color,
|
||||
position: None,
|
||||
}),
|
||||
GradientItem::ComplexColorStop { color, position } => Some(ColorStop {
|
||||
color,
|
||||
position: Some(position),
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert!(stop_items.len() >= 2);
|
||||
|
||||
// Run the algorithm from
|
||||
// https://drafts.csswg.org/css-images-3/#color-stop-syntax
|
||||
|
||||
// Step 1:
|
||||
// If the first color stop does not have a position, set its position to 0%.
|
||||
{
|
||||
let first = stop_items.first_mut().unwrap();
|
||||
if first.position.is_none() {
|
||||
first.position = Some(LengthPercentage::new_percent(Percentage(0.)));
|
||||
}
|
||||
}
|
||||
// If the last color stop does not have a position, set its position to 100%.
|
||||
{
|
||||
let last = stop_items.last_mut().unwrap();
|
||||
if last.position.is_none() {
|
||||
last.position = Some(LengthPercentage::new_percent(Percentage(1.0)));
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Move any stops placed before earlier stops to the
|
||||
// same position as the preceding stop.
|
||||
let mut last_stop_position = stop_items.first().unwrap().position.unwrap();
|
||||
for stop in stop_items.iter_mut().skip(1) {
|
||||
if let Some(pos) = stop.position {
|
||||
if position_to_offset(last_stop_position, total_length) >
|
||||
position_to_offset(pos, total_length)
|
||||
{
|
||||
stop.position = Some(last_stop_position);
|
||||
}
|
||||
last_stop_position = stop.position.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Evenly space stops without position.
|
||||
let mut stops = GradientBuilder::new();
|
||||
let mut stop_run = None;
|
||||
for (i, stop) in stop_items.iter().enumerate() {
|
||||
let offset = match stop.position {
|
||||
None => {
|
||||
if stop_run.is_none() {
|
||||
// Initialize a new stop run.
|
||||
// `unwrap()` here should never fail because this is the beginning of
|
||||
// a stop run, which is always bounded by a length or percentage.
|
||||
let start_offset =
|
||||
position_to_offset(stop_items[i - 1].position.unwrap(), total_length);
|
||||
// `unwrap()` here should never fail because this is the end of
|
||||
// a stop run, which is always bounded by a length or percentage.
|
||||
let (end_index, end_stop) = stop_items[(i + 1)..]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_, ref stop)| stop.position.is_some())
|
||||
.unwrap();
|
||||
let end_offset = position_to_offset(end_stop.position.unwrap(), total_length);
|
||||
stop_run = Some(StopRun {
|
||||
start_offset,
|
||||
end_offset,
|
||||
start_index: i - 1,
|
||||
stop_count: end_index,
|
||||
})
|
||||
}
|
||||
|
||||
let stop_run = stop_run.unwrap();
|
||||
let stop_run_length = stop_run.end_offset - stop_run.start_offset;
|
||||
stop_run.start_offset +
|
||||
stop_run_length * (i - stop_run.start_index) as f32 /
|
||||
((2 + stop_run.stop_count) as f32)
|
||||
},
|
||||
Some(position) => {
|
||||
stop_run = None;
|
||||
position_to_offset(position, total_length)
|
||||
},
|
||||
};
|
||||
assert!(offset.is_finite());
|
||||
stops.push(GradientStop {
|
||||
offset: offset,
|
||||
color: style.resolve_color(stop.color).to_layout(),
|
||||
})
|
||||
}
|
||||
stops
|
||||
}
|
||||
|
||||
fn extend_mode(repeating: bool) -> ExtendMode {
|
||||
if repeating {
|
||||
ExtendMode::Repeat
|
||||
} else {
|
||||
ExtendMode::Clamp
|
||||
}
|
||||
}
|
||||
/// Returns the the distance to the nearest or farthest corner depending on the comperator.
|
||||
fn distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
|
||||
where
|
||||
F: Fn(Au, Au) -> Au,
|
||||
{
|
||||
let dist = distance_to_sides(size, center, cmp);
|
||||
Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
|
||||
}
|
||||
|
||||
/// Returns the distance to the nearest or farthest sides depending on the comparator.
|
||||
///
|
||||
/// The first return value is horizontal distance the second vertical distance.
|
||||
fn distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
||||
where
|
||||
F: Fn(Au, Au) -> Au,
|
||||
{
|
||||
let top_side = center.y;
|
||||
let right_side = size.width - center.x;
|
||||
let bottom_side = size.height - center.y;
|
||||
let left_side = center.x;
|
||||
Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
|
||||
}
|
||||
|
||||
fn position_to_offset(position: LengthPercentage, total_length: Au) -> f32 {
|
||||
if total_length == Au(0) {
|
||||
return 0.0;
|
||||
}
|
||||
position.to_used_value(total_length).0 as f32 / total_length.0 as f32
|
||||
}
|
||||
|
||||
pub fn linear(
|
||||
style: &ComputedValues,
|
||||
size: Size2D<Au>,
|
||||
stops: &[GradientItem],
|
||||
direction: LineDirection,
|
||||
repeating: bool,
|
||||
) -> (Gradient, Vec<GradientStop>) {
|
||||
use style::values::specified::position::HorizontalPositionKeyword::*;
|
||||
use style::values::specified::position::VerticalPositionKeyword::*;
|
||||
let angle = match direction {
|
||||
LineDirection::Angle(angle) => angle.radians(),
|
||||
LineDirection::Horizontal(x) => match x {
|
||||
Left => Angle::from_degrees(270.).radians(),
|
||||
Right => Angle::from_degrees(90.).radians(),
|
||||
},
|
||||
LineDirection::Vertical(y) => match y {
|
||||
Top => Angle::from_degrees(0.).radians(),
|
||||
Bottom => Angle::from_degrees(180.).radians(),
|
||||
},
|
||||
LineDirection::Corner(horizontal, vertical) => {
|
||||
// This the angle for one of the diagonals of the box. Our angle
|
||||
// will either be this one, this one + PI, or one of the other
|
||||
// two perpendicular angles.
|
||||
let atan = (size.height.to_f32_px() / size.width.to_f32_px()).atan();
|
||||
match (horizontal, vertical) {
|
||||
(Right, Bottom) => ::std::f32::consts::PI - atan,
|
||||
(Left, Bottom) => ::std::f32::consts::PI + atan,
|
||||
(Right, Top) => atan,
|
||||
(Left, Top) => -atan,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Get correct gradient line length, based on:
|
||||
// https://drafts.csswg.org/css-images-3/#linear-gradients
|
||||
let dir = Point2D::new(angle.sin(), -angle.cos());
|
||||
|
||||
let line_length =
|
||||
(dir.x * size.width.to_f32_px()).abs() + (dir.y * size.height.to_f32_px()).abs();
|
||||
|
||||
let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
|
||||
|
||||
// This is the vector between the center and the ending point; i.e. half
|
||||
// of the distance between the starting point and the ending point.
|
||||
let delta = Vector2D::new(
|
||||
Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
|
||||
Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0),
|
||||
);
|
||||
|
||||
// This is the length of the gradient line.
|
||||
let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
|
||||
|
||||
let mut builder = convert_gradient_stops(style, stops, length);
|
||||
|
||||
let center = Point2D::new(size.width / 2, size.height / 2);
|
||||
|
||||
(
|
||||
builder.gradient(
|
||||
(center - delta).to_layout(),
|
||||
(center + delta).to_layout(),
|
||||
extend_mode(repeating),
|
||||
),
|
||||
builder.into_stops(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn radial(
|
||||
style: &ComputedValues,
|
||||
size: Size2D<Au>,
|
||||
stops: &[GradientItem],
|
||||
shape: EndingShape,
|
||||
center: Position,
|
||||
repeating: bool,
|
||||
) -> (RadialGradient, Vec<GradientStop>) {
|
||||
let center = Point2D::new(
|
||||
center.horizontal.to_used_value(size.width),
|
||||
center.vertical.to_used_value(size.height),
|
||||
);
|
||||
let radius = match shape {
|
||||
EndingShape::Circle(Circle::Radius(length)) => {
|
||||
let length = Au::from(length);
|
||||
Size2D::new(length, length)
|
||||
},
|
||||
EndingShape::Circle(Circle::Extent(extent)) => circle_size_keyword(extent, &size, ¢er),
|
||||
EndingShape::Ellipse(Ellipse::Radii(x, y)) => {
|
||||
Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height))
|
||||
},
|
||||
EndingShape::Ellipse(Ellipse::Extent(extent)) => {
|
||||
ellipse_size_keyword(extent, &size, ¢er)
|
||||
},
|
||||
};
|
||||
|
||||
let mut builder = convert_gradient_stops(style, stops, radius.width);
|
||||
(
|
||||
builder.radial_gradient(
|
||||
center.to_layout(),
|
||||
radius.to_layout(),
|
||||
extend_mode(repeating),
|
||||
),
|
||||
builder.into_stops(),
|
||||
)
|
||||
}
|
795
components/layout_2020/display_list/items.rs
Normal file
795
components/layout_2020/display_list/items.rs
Normal file
|
@ -0,0 +1,795 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Servo heavily uses display lists, which are retained-mode lists of painting commands to
|
||||
//! perform. Using a list instead of painting elements in immediate mode allows transforms, hit
|
||||
//! testing, and invalidation to be performed using the same primitives as painting. It also allows
|
||||
//! Servo to aggressively cull invisible and out-of-bounds painting elements, to reduce overdraw.
|
||||
//!
|
||||
//! Display items describe relatively high-level drawing operations (for example, entire borders
|
||||
//! and shadows instead of lines and blur operations), to reduce the amount of allocation required.
|
||||
//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of
|
||||
//! low-level drawing primitives.
|
||||
|
||||
use euclid::{SideOffsets2D, Vector2D};
|
||||
use gfx_traits::print_tree::PrintTree;
|
||||
use gfx_traits::{self, StackingContextId};
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use net_traits::image::base::Image;
|
||||
use servo_geometry::MaxRect;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::f32;
|
||||
use std::fmt;
|
||||
use style::computed_values::_servo_top_layer::T as InTopLayer;
|
||||
use webrender_api as wr;
|
||||
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
|
||||
use webrender_api::{BorderRadius, ClipId, ClipMode, CommonItemProperties, ComplexClipRegion};
|
||||
use webrender_api::{ExternalScrollId, FilterOp, GlyphInstance, GradientStop, ImageKey};
|
||||
use webrender_api::{MixBlendMode, ScrollSensitivity, Shadow, SpatialId};
|
||||
use webrender_api::{StickyOffsetBounds, TransformStyle};
|
||||
|
||||
pub use style::dom::OpaqueNode;
|
||||
|
||||
/// The factor that we multiply the blur radius by in order to inflate the boundaries of display
|
||||
/// items that involve a blur. This ensures that the display item boundaries include all the ink.
|
||||
pub static BLUR_INFLATION_FACTOR: i32 = 3;
|
||||
|
||||
/// An index into the vector of ClipScrollNodes. During WebRender conversion these nodes
|
||||
/// are given ClipIds.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
pub struct ClipScrollNodeIndex(usize);
|
||||
|
||||
impl ClipScrollNodeIndex {
|
||||
pub fn root_scroll_node() -> ClipScrollNodeIndex {
|
||||
ClipScrollNodeIndex(1)
|
||||
}
|
||||
|
||||
pub fn root_reference_frame() -> ClipScrollNodeIndex {
|
||||
ClipScrollNodeIndex(0)
|
||||
}
|
||||
|
||||
pub fn new(index: usize) -> ClipScrollNodeIndex {
|
||||
assert_ne!(index, 0, "Use the root_reference_frame constructor");
|
||||
assert_ne!(index, 1, "Use the root_scroll_node constructor");
|
||||
ClipScrollNodeIndex(index)
|
||||
}
|
||||
|
||||
pub fn is_root_scroll_node(&self) -> bool {
|
||||
*self == Self::root_scroll_node()
|
||||
}
|
||||
|
||||
pub fn to_define_item(&self) -> DisplayItem {
|
||||
DisplayItem::DefineClipScrollNode(Box::new(DefineClipScrollNodeItem {
|
||||
base: BaseDisplayItem::empty(),
|
||||
node_index: *self,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn to_index(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of indices into the clip scroll node vector for a given item.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
pub struct ClippingAndScrolling {
|
||||
pub scrolling: ClipScrollNodeIndex,
|
||||
pub clipping: Option<ClipScrollNodeIndex>,
|
||||
}
|
||||
|
||||
impl ClippingAndScrolling {
|
||||
pub fn simple(scrolling: ClipScrollNodeIndex) -> ClippingAndScrolling {
|
||||
ClippingAndScrolling {
|
||||
scrolling,
|
||||
clipping: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(scrolling: ClipScrollNodeIndex, clipping: ClipScrollNodeIndex) -> Self {
|
||||
ClippingAndScrolling {
|
||||
scrolling,
|
||||
clipping: Some(clipping),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DisplayList {
|
||||
pub list: Vec<DisplayItem>,
|
||||
pub clip_scroll_nodes: Vec<ClipScrollNode>,
|
||||
}
|
||||
|
||||
impl DisplayList {
|
||||
/// Return the bounds of this display list based on the dimensions of the root
|
||||
/// stacking context.
|
||||
pub fn bounds(&self) -> LayoutRect {
|
||||
match self.list.get(0) {
|
||||
Some(&DisplayItem::PushStackingContext(ref item)) => item.stacking_context.bounds,
|
||||
Some(_) => unreachable!("Root element of display list not stacking context."),
|
||||
None => LayoutRect::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print(&self) {
|
||||
let mut print_tree = PrintTree::new("Display List".to_owned());
|
||||
self.print_with_tree(&mut print_tree);
|
||||
}
|
||||
|
||||
pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
|
||||
print_tree.new_level("ClipScrollNodes".to_owned());
|
||||
for node in &self.clip_scroll_nodes {
|
||||
print_tree.add_item(format!("{:?}", node));
|
||||
}
|
||||
print_tree.end_level();
|
||||
|
||||
print_tree.new_level("Items".to_owned());
|
||||
for item in &self.list {
|
||||
print_tree.add_item(format!(
|
||||
"{:?} StackingContext: {:?} {:?}",
|
||||
item,
|
||||
item.base().stacking_context_id,
|
||||
item.clipping_and_scrolling()
|
||||
));
|
||||
}
|
||||
print_tree.end_level();
|
||||
}
|
||||
}
|
||||
|
||||
impl gfx_traits::DisplayList for DisplayList {
|
||||
/// Analyze the display list to figure out if this may be the first
|
||||
/// contentful paint (i.e. the display list contains items of type text,
|
||||
/// image, non-white canvas or SVG). Used by metrics.
|
||||
fn is_contentful(&self) -> bool {
|
||||
for item in &self.list {
|
||||
match item {
|
||||
&DisplayItem::Text(_) | &DisplayItem::Image(_) => return true,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Display list sections that make up a stacking context. Each section here refers
|
||||
/// to the steps in CSS 2.1 Appendix E.
|
||||
///
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub enum DisplayListSection {
|
||||
BackgroundAndBorders,
|
||||
BlockBackgroundsAndBorders,
|
||||
Content,
|
||||
Outlines,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub enum StackingContextType {
|
||||
Real,
|
||||
PseudoPositioned,
|
||||
PseudoFloat,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
/// Represents one CSS stacking context, which may or may not have a hardware layer.
|
||||
pub struct StackingContext {
|
||||
/// The ID of this StackingContext for uniquely identifying it.
|
||||
pub id: StackingContextId,
|
||||
|
||||
/// The type of this StackingContext. Used for collecting and sorting.
|
||||
pub context_type: StackingContextType,
|
||||
|
||||
/// The position and size of this stacking context.
|
||||
pub bounds: LayoutRect,
|
||||
|
||||
/// The overflow rect for this stacking context in its coordinate system.
|
||||
pub overflow: LayoutRect,
|
||||
|
||||
/// The `z-index` for this stacking context.
|
||||
pub z_index: i32,
|
||||
|
||||
/// Whether this is the top layer.
|
||||
pub in_top_layer: InTopLayer,
|
||||
|
||||
/// CSS filters to be applied to this stacking context (including opacity).
|
||||
pub filters: Vec<FilterOp>,
|
||||
|
||||
/// The blend mode with which this stacking context blends with its backdrop.
|
||||
pub mix_blend_mode: MixBlendMode,
|
||||
|
||||
/// A transform to be applied to this stacking context.
|
||||
pub transform: Option<LayoutTransform>,
|
||||
|
||||
/// The transform style of this stacking context.
|
||||
pub transform_style: TransformStyle,
|
||||
|
||||
/// The perspective matrix to be applied to children.
|
||||
pub perspective: Option<LayoutTransform>,
|
||||
|
||||
/// The clip and scroll info for this StackingContext.
|
||||
pub parent_clipping_and_scrolling: ClippingAndScrolling,
|
||||
|
||||
/// The index of the reference frame that this stacking context estalishes.
|
||||
pub established_reference_frame: Option<ClipScrollNodeIndex>,
|
||||
}
|
||||
|
||||
impl StackingContext {
|
||||
/// Creates a new stacking context.
|
||||
#[inline]
|
||||
pub fn new(
|
||||
id: StackingContextId,
|
||||
context_type: StackingContextType,
|
||||
bounds: LayoutRect,
|
||||
overflow: LayoutRect,
|
||||
z_index: i32,
|
||||
in_top_layer: InTopLayer,
|
||||
filters: Vec<FilterOp>,
|
||||
mix_blend_mode: MixBlendMode,
|
||||
transform: Option<LayoutTransform>,
|
||||
transform_style: TransformStyle,
|
||||
perspective: Option<LayoutTransform>,
|
||||
parent_clipping_and_scrolling: ClippingAndScrolling,
|
||||
established_reference_frame: Option<ClipScrollNodeIndex>,
|
||||
) -> StackingContext {
|
||||
StackingContext {
|
||||
id,
|
||||
context_type,
|
||||
bounds,
|
||||
overflow,
|
||||
z_index,
|
||||
in_top_layer,
|
||||
filters,
|
||||
mix_blend_mode,
|
||||
transform,
|
||||
transform_style,
|
||||
perspective,
|
||||
parent_clipping_and_scrolling,
|
||||
established_reference_frame,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn root() -> StackingContext {
|
||||
StackingContext::new(
|
||||
StackingContextId::root(),
|
||||
StackingContextType::Real,
|
||||
LayoutRect::zero(),
|
||||
LayoutRect::zero(),
|
||||
0,
|
||||
InTopLayer::None,
|
||||
vec![],
|
||||
MixBlendMode::Normal,
|
||||
None,
|
||||
TransformStyle::Flat,
|
||||
None,
|
||||
ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_display_list_items(self) -> (DisplayItem, DisplayItem) {
|
||||
let mut base_item = BaseDisplayItem::empty();
|
||||
base_item.stacking_context_id = self.id;
|
||||
base_item.clipping_and_scrolling = self.parent_clipping_and_scrolling;
|
||||
|
||||
let pop_item = DisplayItem::PopStackingContext(Box::new(PopStackingContextItem {
|
||||
base: base_item.clone(),
|
||||
stacking_context_id: self.id,
|
||||
}));
|
||||
|
||||
let push_item = DisplayItem::PushStackingContext(Box::new(PushStackingContextItem {
|
||||
base: base_item,
|
||||
stacking_context: self,
|
||||
}));
|
||||
|
||||
(push_item, pop_item)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for StackingContext {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
if self.in_top_layer == InTopLayer::Top {
|
||||
if other.in_top_layer == InTopLayer::Top {
|
||||
return Ordering::Equal;
|
||||
} else {
|
||||
return Ordering::Greater;
|
||||
}
|
||||
} else if other.in_top_layer == InTopLayer::Top {
|
||||
return Ordering::Less;
|
||||
}
|
||||
|
||||
if self.z_index != 0 || other.z_index != 0 {
|
||||
return self.z_index.cmp(&other.z_index);
|
||||
}
|
||||
|
||||
match (self.context_type, other.context_type) {
|
||||
(StackingContextType::PseudoFloat, StackingContextType::PseudoFloat) => Ordering::Equal,
|
||||
(StackingContextType::PseudoFloat, _) => Ordering::Less,
|
||||
(_, StackingContextType::PseudoFloat) => Ordering::Greater,
|
||||
(_, _) => Ordering::Equal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for StackingContext {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for StackingContext {}
|
||||
impl PartialEq for StackingContext {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for StackingContext {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let type_string = if self.context_type == StackingContextType::Real {
|
||||
"StackingContext"
|
||||
} else {
|
||||
"Pseudo-StackingContext"
|
||||
};
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} at {:?} with overflow {:?}: {:?}",
|
||||
type_string, self.bounds, self.overflow, self.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
pub struct StickyFrameData {
|
||||
pub margins: SideOffsets2D<Option<f32>>,
|
||||
pub vertical_offset_bounds: StickyOffsetBounds,
|
||||
pub horizontal_offset_bounds: StickyOffsetBounds,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
pub enum ClipScrollNodeType {
|
||||
Placeholder,
|
||||
ScrollFrame(ScrollSensitivity, ExternalScrollId),
|
||||
StickyFrame(StickyFrameData),
|
||||
Clip,
|
||||
}
|
||||
|
||||
/// Defines a clip scroll node.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct ClipScrollNode {
|
||||
/// The index of the parent of this ClipScrollNode.
|
||||
pub parent_index: ClipScrollNodeIndex,
|
||||
|
||||
/// The position of this scroll root's frame in the parent stacking context.
|
||||
pub clip: ClippingRegion,
|
||||
|
||||
/// The rect of the contents that can be scrolled inside of the scroll root.
|
||||
pub content_rect: LayoutRect,
|
||||
|
||||
/// The type of this ClipScrollNode.
|
||||
pub node_type: ClipScrollNodeType,
|
||||
}
|
||||
|
||||
impl ClipScrollNode {
|
||||
pub fn placeholder() -> ClipScrollNode {
|
||||
ClipScrollNode {
|
||||
parent_index: ClipScrollNodeIndex(0),
|
||||
clip: ClippingRegion::from_rect(LayoutRect::zero()),
|
||||
content_rect: LayoutRect::zero(),
|
||||
node_type: ClipScrollNodeType::Placeholder,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_placeholder(&self) -> bool {
|
||||
self.node_type == ClipScrollNodeType::Placeholder
|
||||
}
|
||||
}
|
||||
|
||||
/// One drawing command in the list.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub enum DisplayItem {
|
||||
Rectangle(Box<CommonDisplayItem<wr::RectangleDisplayItem>>),
|
||||
Text(Box<CommonDisplayItem<wr::TextDisplayItem, Vec<GlyphInstance>>>),
|
||||
Image(Box<CommonDisplayItem<wr::ImageDisplayItem>>),
|
||||
Border(Box<CommonDisplayItem<wr::BorderDisplayItem, Vec<GradientStop>>>),
|
||||
Gradient(Box<CommonDisplayItem<wr::GradientDisplayItem, Vec<GradientStop>>>),
|
||||
RadialGradient(Box<CommonDisplayItem<wr::RadialGradientDisplayItem, Vec<GradientStop>>>),
|
||||
Line(Box<CommonDisplayItem<wr::LineDisplayItem>>),
|
||||
BoxShadow(Box<CommonDisplayItem<wr::BoxShadowDisplayItem>>),
|
||||
PushTextShadow(Box<PushTextShadowDisplayItem>),
|
||||
PopAllTextShadows(Box<PopAllTextShadowsDisplayItem>),
|
||||
Iframe(Box<IframeDisplayItem>),
|
||||
PushStackingContext(Box<PushStackingContextItem>),
|
||||
PopStackingContext(Box<PopStackingContextItem>),
|
||||
DefineClipScrollNode(Box<DefineClipScrollNodeItem>),
|
||||
}
|
||||
|
||||
/// Information common to all display items.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct BaseDisplayItem {
|
||||
/// Metadata attached to this display item.
|
||||
pub metadata: DisplayItemMetadata,
|
||||
|
||||
/// The clip rectangle to use for this item.
|
||||
pub clip_rect: LayoutRect,
|
||||
|
||||
/// The section of the display list that this item belongs to.
|
||||
pub section: DisplayListSection,
|
||||
|
||||
/// The id of the stacking context this item belongs to.
|
||||
pub stacking_context_id: StackingContextId,
|
||||
|
||||
/// The clip and scroll info for this item.
|
||||
pub clipping_and_scrolling: ClippingAndScrolling,
|
||||
}
|
||||
|
||||
impl BaseDisplayItem {
|
||||
#[inline(always)]
|
||||
pub fn new(
|
||||
metadata: DisplayItemMetadata,
|
||||
clip_rect: LayoutRect,
|
||||
section: DisplayListSection,
|
||||
stacking_context_id: StackingContextId,
|
||||
clipping_and_scrolling: ClippingAndScrolling,
|
||||
) -> BaseDisplayItem {
|
||||
BaseDisplayItem {
|
||||
metadata,
|
||||
clip_rect,
|
||||
section,
|
||||
stacking_context_id,
|
||||
clipping_and_scrolling,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn empty() -> BaseDisplayItem {
|
||||
BaseDisplayItem {
|
||||
metadata: DisplayItemMetadata {
|
||||
node: OpaqueNode(0),
|
||||
pointing: None,
|
||||
},
|
||||
// Create a rectangle of maximal size.
|
||||
clip_rect: LayoutRect::max_rect(),
|
||||
section: DisplayListSection::Content,
|
||||
stacking_context_id: StackingContextId::root(),
|
||||
clipping_and_scrolling: ClippingAndScrolling::simple(
|
||||
ClipScrollNodeIndex::root_scroll_node(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_common_item_properties() -> CommonItemProperties {
|
||||
CommonItemProperties {
|
||||
clip_rect: LayoutRect::max_rect(),
|
||||
clip_id: ClipId::root(wr::PipelineId::dummy()),
|
||||
spatial_id: SpatialId::root_scroll_node(wr::PipelineId::dummy()),
|
||||
hit_info: None,
|
||||
is_backface_visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A clipping region for a display item. Currently, this can describe rectangles, rounded
|
||||
/// rectangles (for `border-radius`), or arbitrary intersections of the two. Arbitrary transforms
|
||||
/// are not supported because those are handled by the higher-level `StackingContext` abstraction.
|
||||
#[derive(Clone, PartialEq, Serialize)]
|
||||
pub struct ClippingRegion {
|
||||
/// The main rectangular region. This does not include any corners.
|
||||
pub main: LayoutRect,
|
||||
/// Any complex regions.
|
||||
///
|
||||
/// TODO(pcwalton): Atomically reference count these? Not sure if it's worth the trouble.
|
||||
/// Measure and follow up.
|
||||
pub complex: Vec<ComplexClipRegion>,
|
||||
}
|
||||
|
||||
impl ClippingRegion {
|
||||
/// Returns an empty clipping region that, if set, will result in no pixels being visible.
|
||||
#[inline]
|
||||
pub fn empty() -> ClippingRegion {
|
||||
ClippingRegion {
|
||||
main: LayoutRect::zero(),
|
||||
complex: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an all-encompassing clipping region that clips no pixels out.
|
||||
#[inline]
|
||||
pub fn max() -> ClippingRegion {
|
||||
ClippingRegion {
|
||||
main: LayoutRect::max_rect(),
|
||||
complex: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a clipping region that represents the given rectangle.
|
||||
#[inline]
|
||||
pub fn from_rect(rect: LayoutRect) -> ClippingRegion {
|
||||
ClippingRegion {
|
||||
main: rect,
|
||||
complex: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Intersects this clipping region with the given rounded rectangle.
|
||||
#[inline]
|
||||
pub fn intersect_with_rounded_rect(&mut self, rect: LayoutRect, radii: BorderRadius) {
|
||||
let new_complex_region = ComplexClipRegion {
|
||||
rect,
|
||||
radii,
|
||||
mode: ClipMode::Clip,
|
||||
};
|
||||
|
||||
// FIXME(pcwalton): This is O(n²) worst case for disjoint clipping regions. Is that OK?
|
||||
// They're slow anyway…
|
||||
//
|
||||
// Possibly relevant if we want to do better:
|
||||
//
|
||||
// http://www.inrg.csie.ntu.edu.tw/algorithm2014/presentation/D&C%20Lee-84.pdf
|
||||
for existing_complex_region in &mut self.complex {
|
||||
if completely_encloses(&existing_complex_region, &new_complex_region) {
|
||||
*existing_complex_region = new_complex_region;
|
||||
return;
|
||||
}
|
||||
if completely_encloses(&new_complex_region, &existing_complex_region) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.complex.push(new_complex_region);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ClippingRegion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if *self == ClippingRegion::max() {
|
||||
write!(f, "ClippingRegion::Max")
|
||||
} else if *self == ClippingRegion::empty() {
|
||||
write!(f, "ClippingRegion::Empty")
|
||||
} else if self.main == LayoutRect::max_rect() {
|
||||
write!(f, "ClippingRegion(Complex={:?})", self.complex)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"ClippingRegion(Rect={:?}, Complex={:?})",
|
||||
self.main, self.complex
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pcwalton): This could be more aggressive by considering points that touch the inside of
|
||||
// the border radius ellipse.
|
||||
fn completely_encloses(this: &ComplexClipRegion, other: &ComplexClipRegion) -> bool {
|
||||
let left = this.radii.top_left.width.max(this.radii.bottom_left.width);
|
||||
let top = this.radii.top_left.height.max(this.radii.top_right.height);
|
||||
let right = this
|
||||
.radii
|
||||
.top_right
|
||||
.width
|
||||
.max(this.radii.bottom_right.width);
|
||||
let bottom = this
|
||||
.radii
|
||||
.bottom_left
|
||||
.height
|
||||
.max(this.radii.bottom_right.height);
|
||||
let interior = LayoutRect::new(
|
||||
LayoutPoint::new(this.rect.origin.x + left, this.rect.origin.y + top),
|
||||
LayoutSize::new(
|
||||
this.rect.size.width - left - right,
|
||||
this.rect.size.height - top - bottom,
|
||||
),
|
||||
);
|
||||
interior.origin.x <= other.rect.origin.x &&
|
||||
interior.origin.y <= other.rect.origin.y &&
|
||||
interior.max_x() >= other.rect.max_x() &&
|
||||
interior.max_y() >= other.rect.max_y()
|
||||
}
|
||||
|
||||
/// Metadata attached to each display item. This is useful for performing auxiliary threads with
|
||||
/// the display list involving hit testing: finding the originating DOM node and determining the
|
||||
/// cursor to use when the element is hovered over.
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
pub struct DisplayItemMetadata {
|
||||
/// The DOM node from which this display item originated.
|
||||
pub node: OpaqueNode,
|
||||
/// The value of the `cursor` property when the mouse hovers over this display item. If `None`,
|
||||
/// this display item is ineligible for pointer events (`pointer-events: none`).
|
||||
pub pointing: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Serialize)]
|
||||
pub enum TextOrientation {
|
||||
Upright,
|
||||
SidewaysLeft,
|
||||
SidewaysRight,
|
||||
}
|
||||
|
||||
/// Paints an iframe.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct IframeDisplayItem {
|
||||
pub base: BaseDisplayItem,
|
||||
pub iframe: PipelineId,
|
||||
pub bounds: LayoutRect,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct CommonDisplayItem<T, U = ()> {
|
||||
pub base: BaseDisplayItem,
|
||||
pub item: T,
|
||||
pub data: U,
|
||||
}
|
||||
|
||||
impl<T> CommonDisplayItem<T> {
|
||||
pub fn new(base: BaseDisplayItem, item: T) -> Box<CommonDisplayItem<T>> {
|
||||
Box::new(CommonDisplayItem {
|
||||
base,
|
||||
item,
|
||||
data: (),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> CommonDisplayItem<T, U> {
|
||||
pub fn with_data(base: BaseDisplayItem, item: T, data: U) -> Box<CommonDisplayItem<T, U>> {
|
||||
Box::new(CommonDisplayItem { base, item, data })
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a text shadow that affects all items until the paired PopTextShadow.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PushTextShadowDisplayItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
pub shadow: Shadow,
|
||||
}
|
||||
|
||||
/// Defines a text shadow that affects all items until the next PopTextShadow.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PopAllTextShadowsDisplayItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
}
|
||||
|
||||
/// Defines a stacking context.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PushStackingContextItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
pub stacking_context: StackingContext,
|
||||
}
|
||||
|
||||
/// Defines a stacking context.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PopStackingContextItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
pub stacking_context_id: StackingContextId,
|
||||
}
|
||||
|
||||
/// Starts a group of items inside a particular scroll root.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct DefineClipScrollNodeItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
/// The scroll root that this item starts.
|
||||
pub node_index: ClipScrollNodeIndex,
|
||||
}
|
||||
|
||||
impl DisplayItem {
|
||||
pub fn base(&self) -> &BaseDisplayItem {
|
||||
match *self {
|
||||
DisplayItem::Rectangle(ref rect) => &rect.base,
|
||||
DisplayItem::Text(ref text) => &text.base,
|
||||
DisplayItem::Image(ref image_item) => &image_item.base,
|
||||
DisplayItem::Border(ref border) => &border.base,
|
||||
DisplayItem::Gradient(ref gradient) => &gradient.base,
|
||||
DisplayItem::RadialGradient(ref gradient) => &gradient.base,
|
||||
DisplayItem::Line(ref line) => &line.base,
|
||||
DisplayItem::BoxShadow(ref box_shadow) => &box_shadow.base,
|
||||
DisplayItem::PushTextShadow(ref push_text_shadow) => &push_text_shadow.base,
|
||||
DisplayItem::PopAllTextShadows(ref pop_text_shadow) => &pop_text_shadow.base,
|
||||
DisplayItem::Iframe(ref iframe) => &iframe.base,
|
||||
DisplayItem::PushStackingContext(ref stacking_context) => &stacking_context.base,
|
||||
DisplayItem::PopStackingContext(ref item) => &item.base,
|
||||
DisplayItem::DefineClipScrollNode(ref item) => &item.base,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clipping_and_scrolling(&self) -> ClippingAndScrolling {
|
||||
self.base().clipping_and_scrolling
|
||||
}
|
||||
|
||||
pub fn stacking_context_id(&self) -> StackingContextId {
|
||||
self.base().stacking_context_id
|
||||
}
|
||||
|
||||
pub fn section(&self) -> DisplayListSection {
|
||||
self.base().section
|
||||
}
|
||||
|
||||
pub fn bounds(&self) -> LayoutRect {
|
||||
match *self {
|
||||
DisplayItem::Rectangle(ref item) => item.item.common.clip_rect,
|
||||
DisplayItem::Text(ref item) => item.item.bounds,
|
||||
DisplayItem::Image(ref item) => item.item.bounds,
|
||||
DisplayItem::Border(ref item) => item.item.bounds,
|
||||
DisplayItem::Gradient(ref item) => item.item.bounds,
|
||||
DisplayItem::RadialGradient(ref item) => item.item.bounds,
|
||||
DisplayItem::Line(ref item) => item.item.area,
|
||||
DisplayItem::BoxShadow(ref item) => item.item.box_bounds,
|
||||
DisplayItem::PushTextShadow(_) => LayoutRect::zero(),
|
||||
DisplayItem::PopAllTextShadows(_) => LayoutRect::zero(),
|
||||
DisplayItem::Iframe(ref item) => item.bounds,
|
||||
DisplayItem::PushStackingContext(ref item) => item.stacking_context.bounds,
|
||||
DisplayItem::PopStackingContext(_) => LayoutRect::zero(),
|
||||
DisplayItem::DefineClipScrollNode(_) => LayoutRect::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for DisplayItem {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let DisplayItem::PushStackingContext(ref item) = *self {
|
||||
return write!(f, "PushStackingContext({:?})", item.stacking_context);
|
||||
}
|
||||
|
||||
if let DisplayItem::PopStackingContext(ref item) = *self {
|
||||
return write!(f, "PopStackingContext({:?}", item.stacking_context_id);
|
||||
}
|
||||
|
||||
if let DisplayItem::DefineClipScrollNode(ref item) = *self {
|
||||
return write!(f, "DefineClipScrollNode({:?}", item.node_index);
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} @ {:?} {:?}",
|
||||
match *self {
|
||||
DisplayItem::Rectangle(_) => "Rectangle".to_owned(),
|
||||
DisplayItem::Text(_) => "Text".to_owned(),
|
||||
DisplayItem::Image(_) => "Image".to_owned(),
|
||||
DisplayItem::Border(_) => "Border".to_owned(),
|
||||
DisplayItem::Gradient(_) => "Gradient".to_owned(),
|
||||
DisplayItem::RadialGradient(_) => "RadialGradient".to_owned(),
|
||||
DisplayItem::Line(_) => "Line".to_owned(),
|
||||
DisplayItem::BoxShadow(_) => "BoxShadow".to_owned(),
|
||||
DisplayItem::PushTextShadow(_) => "PushTextShadow".to_owned(),
|
||||
DisplayItem::PopAllTextShadows(_) => "PopTextShadow".to_owned(),
|
||||
DisplayItem::Iframe(_) => "Iframe".to_owned(),
|
||||
DisplayItem::PushStackingContext(_) |
|
||||
DisplayItem::PopStackingContext(_) |
|
||||
DisplayItem::DefineClipScrollNode(_) => "".to_owned(),
|
||||
},
|
||||
self.bounds(),
|
||||
self.base().clip_rect
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
pub struct WebRenderImageInfo {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub key: Option<ImageKey>,
|
||||
}
|
||||
|
||||
impl WebRenderImageInfo {
|
||||
#[inline]
|
||||
pub fn from_image(image: &Image) -> WebRenderImageInfo {
|
||||
WebRenderImageInfo {
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
key: image.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of the scroll offset list. This is only populated if WebRender is in use.
|
||||
pub type ScrollOffsetMap = HashMap<ExternalScrollId, Vector2D<f32>>;
|
19
components/layout_2020/display_list/mod.rs
Normal file
19
components/layout_2020/display_list/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
/* 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/. */
|
||||
|
||||
pub use self::builder::BorderPaintingMode;
|
||||
pub use self::builder::DisplayListBuildState;
|
||||
pub use self::builder::IndexableText;
|
||||
pub use self::builder::StackingContextCollectionFlags;
|
||||
pub use self::builder::StackingContextCollectionState;
|
||||
pub use self::conversions::ToLayout;
|
||||
pub use self::webrender_helpers::WebRenderDisplayListConverter;
|
||||
|
||||
mod background;
|
||||
mod border;
|
||||
mod builder;
|
||||
mod conversions;
|
||||
mod gradient;
|
||||
pub mod items;
|
||||
mod webrender_helpers;
|
321
components/layout_2020/display_list/webrender_helpers.rs
Normal file
321
components/layout_2020/display_list/webrender_helpers.rs
Normal file
|
@ -0,0 +1,321 @@
|
|||
/* 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/. */
|
||||
|
||||
// TODO(gw): This contains helper traits and implementations for converting Servo display lists
|
||||
// into WebRender display lists. In the future, this step should be completely removed.
|
||||
// This might be achieved by sharing types between WR and Servo display lists, or
|
||||
// completely converting layout to directly generate WebRender display lists, for example.
|
||||
|
||||
use crate::display_list::items::{BaseDisplayItem, ClipScrollNode, ClipScrollNodeType};
|
||||
use crate::display_list::items::{DisplayItem, DisplayList, StackingContextType};
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use webrender_api::units::LayoutPoint;
|
||||
use webrender_api::{self, ClipId, CommonItemProperties, DisplayItem as WrDisplayItem};
|
||||
use webrender_api::{DisplayListBuilder, PropertyBinding, PushStackingContextDisplayItem};
|
||||
use webrender_api::{
|
||||
RasterSpace, ReferenceFrameKind, SpaceAndClipInfo, SpatialId, StackingContext,
|
||||
};
|
||||
|
||||
pub trait WebRenderDisplayListConverter {
|
||||
fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder;
|
||||
}
|
||||
|
||||
struct ClipScrollState {
|
||||
clip_ids: Vec<Option<ClipId>>,
|
||||
spatial_ids: Vec<Option<SpatialId>>,
|
||||
active_clip_id: ClipId,
|
||||
active_spatial_id: SpatialId,
|
||||
}
|
||||
|
||||
trait WebRenderDisplayItemConverter {
|
||||
fn convert_to_webrender(
|
||||
&mut self,
|
||||
clip_scroll_nodes: &[ClipScrollNode],
|
||||
state: &mut ClipScrollState,
|
||||
builder: &mut DisplayListBuilder,
|
||||
);
|
||||
}
|
||||
|
||||
impl WebRenderDisplayListConverter for DisplayList {
|
||||
fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder {
|
||||
let mut clip_ids = vec![None; self.clip_scroll_nodes.len()];
|
||||
let mut spatial_ids = vec![None; self.clip_scroll_nodes.len()];
|
||||
|
||||
// We need to add the WebRender root reference frame and root scroll node ids
|
||||
// here manually, because WebRender creates these automatically.
|
||||
// We also follow the "old" WebRender API for clip/scroll for now,
|
||||
// hence both arrays are initialized based on FIRST_SPATIAL_NODE_INDEX,
|
||||
// while FIRST_CLIP_NODE_INDEX is not taken into account.
|
||||
|
||||
let webrender_pipeline = pipeline_id.to_webrender();
|
||||
clip_ids[0] = Some(ClipId::root(webrender_pipeline));
|
||||
clip_ids[1] = Some(ClipId::root(webrender_pipeline));
|
||||
spatial_ids[0] = Some(SpatialId::root_reference_frame(webrender_pipeline));
|
||||
spatial_ids[1] = Some(SpatialId::root_scroll_node(webrender_pipeline));
|
||||
|
||||
let mut state = ClipScrollState {
|
||||
clip_ids,
|
||||
spatial_ids,
|
||||
active_clip_id: ClipId::root(webrender_pipeline),
|
||||
active_spatial_id: SpatialId::root_scroll_node(webrender_pipeline),
|
||||
};
|
||||
|
||||
let mut builder = DisplayListBuilder::with_capacity(
|
||||
webrender_pipeline,
|
||||
self.bounds().size,
|
||||
1024 * 1024, // 1 MB of space
|
||||
);
|
||||
|
||||
for item in &mut self.list {
|
||||
item.convert_to_webrender(&self.clip_scroll_nodes, &mut state, &mut builder);
|
||||
}
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
impl WebRenderDisplayItemConverter for DisplayItem {
|
||||
fn convert_to_webrender(
|
||||
&mut self,
|
||||
clip_scroll_nodes: &[ClipScrollNode],
|
||||
state: &mut ClipScrollState,
|
||||
builder: &mut DisplayListBuilder,
|
||||
) {
|
||||
// Note: for each time of a display item, if we register one of `clip_ids` or `spatial_ids`,
|
||||
// we also register the other one as inherited from the current state or the stack.
|
||||
// This is not an ideal behavior, but it is compatible with the old WebRender model
|
||||
// of the clip-scroll tree.
|
||||
|
||||
let clip_and_scroll_indices = self.base().clipping_and_scrolling;
|
||||
trace!("converting {:?}", clip_and_scroll_indices);
|
||||
|
||||
let cur_spatial_id = state.spatial_ids[clip_and_scroll_indices.scrolling.to_index()]
|
||||
.expect("Tried to use WebRender SpatialId before it was defined.");
|
||||
if cur_spatial_id != state.active_spatial_id {
|
||||
state.active_spatial_id = cur_spatial_id;
|
||||
}
|
||||
|
||||
let internal_clip_id = clip_and_scroll_indices
|
||||
.clipping
|
||||
.unwrap_or(clip_and_scroll_indices.scrolling);
|
||||
let cur_clip_id = state.clip_ids[internal_clip_id.to_index()]
|
||||
.expect("Tried to use WebRender ClipId before it was defined.");
|
||||
if cur_clip_id != state.active_clip_id {
|
||||
state.active_clip_id = cur_clip_id;
|
||||
}
|
||||
|
||||
match *self {
|
||||
DisplayItem::Rectangle(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_item(&WrDisplayItem::Rectangle(item.item));
|
||||
},
|
||||
DisplayItem::Text(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_item(&WrDisplayItem::Text(item.item));
|
||||
builder.push_iter(item.data.iter());
|
||||
},
|
||||
DisplayItem::Image(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_item(&WrDisplayItem::Image(item.item));
|
||||
},
|
||||
DisplayItem::Border(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
if !item.data.is_empty() {
|
||||
builder.push_stops(item.data.as_ref());
|
||||
}
|
||||
builder.push_item(&WrDisplayItem::Border(item.item));
|
||||
},
|
||||
DisplayItem::Gradient(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_stops(item.data.as_ref());
|
||||
builder.push_item(&WrDisplayItem::Gradient(item.item));
|
||||
},
|
||||
DisplayItem::RadialGradient(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_stops(item.data.as_ref());
|
||||
builder.push_item(&WrDisplayItem::RadialGradient(item.item));
|
||||
},
|
||||
DisplayItem::Line(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_item(&WrDisplayItem::Line(item.item));
|
||||
},
|
||||
DisplayItem::BoxShadow(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_item(&WrDisplayItem::BoxShadow(item.item));
|
||||
},
|
||||
DisplayItem::PushTextShadow(ref mut item) => {
|
||||
let common = build_common_item_properties(&item.base, state);
|
||||
builder.push_shadow(
|
||||
&SpaceAndClipInfo {
|
||||
spatial_id: common.spatial_id,
|
||||
clip_id: common.clip_id,
|
||||
},
|
||||
item.shadow,
|
||||
true,
|
||||
);
|
||||
},
|
||||
DisplayItem::PopAllTextShadows(_) => {
|
||||
builder.push_item(&WrDisplayItem::PopAllShadows);
|
||||
},
|
||||
DisplayItem::Iframe(ref mut item) => {
|
||||
let common = build_common_item_properties(&item.base, state);
|
||||
builder.push_iframe(
|
||||
item.bounds,
|
||||
common.clip_rect,
|
||||
&SpaceAndClipInfo {
|
||||
spatial_id: common.spatial_id,
|
||||
clip_id: common.clip_id,
|
||||
},
|
||||
item.iframe.to_webrender(),
|
||||
true,
|
||||
);
|
||||
},
|
||||
DisplayItem::PushStackingContext(ref mut item) => {
|
||||
let stacking_context = &item.stacking_context;
|
||||
debug_assert_eq!(stacking_context.context_type, StackingContextType::Real);
|
||||
|
||||
//let mut info = webrender_api::LayoutPrimitiveInfo::new(stacking_context.bounds);
|
||||
let mut bounds = stacking_context.bounds;
|
||||
let spatial_id =
|
||||
if let Some(frame_index) = stacking_context.established_reference_frame {
|
||||
let (transform, ref_frame) =
|
||||
match (stacking_context.transform, stacking_context.perspective) {
|
||||
(None, Some(p)) => (
|
||||
p,
|
||||
ReferenceFrameKind::Perspective {
|
||||
scrolling_relative_to: None,
|
||||
},
|
||||
),
|
||||
(Some(t), None) => (t, ReferenceFrameKind::Transform),
|
||||
(Some(t), Some(p)) => (
|
||||
t.pre_mul(&p),
|
||||
ReferenceFrameKind::Perspective {
|
||||
scrolling_relative_to: None,
|
||||
},
|
||||
),
|
||||
(None, None) => unreachable!(),
|
||||
};
|
||||
|
||||
let spatial_id = builder.push_reference_frame(
|
||||
stacking_context.bounds.origin,
|
||||
state.active_spatial_id,
|
||||
stacking_context.transform_style,
|
||||
PropertyBinding::Value(transform),
|
||||
ref_frame,
|
||||
);
|
||||
|
||||
state.spatial_ids[frame_index.to_index()] = Some(spatial_id);
|
||||
state.clip_ids[frame_index.to_index()] = Some(cur_clip_id);
|
||||
|
||||
bounds.origin = LayoutPoint::zero();
|
||||
spatial_id
|
||||
} else {
|
||||
state.active_spatial_id
|
||||
};
|
||||
|
||||
if !stacking_context.filters.is_empty() {
|
||||
builder.push_item(&WrDisplayItem::SetFilterOps);
|
||||
builder.push_iter(&stacking_context.filters);
|
||||
}
|
||||
|
||||
let wr_item = PushStackingContextDisplayItem {
|
||||
origin: bounds.origin,
|
||||
spatial_id,
|
||||
is_backface_visible: true,
|
||||
stacking_context: StackingContext {
|
||||
transform_style: stacking_context.transform_style,
|
||||
mix_blend_mode: stacking_context.mix_blend_mode,
|
||||
clip_id: None,
|
||||
raster_space: RasterSpace::Screen,
|
||||
// TODO(pcwalton): Enable picture caching?
|
||||
cache_tiles: false,
|
||||
},
|
||||
};
|
||||
|
||||
builder.push_item(&WrDisplayItem::PushStackingContext(wr_item));
|
||||
},
|
||||
DisplayItem::PopStackingContext(_) => builder.pop_stacking_context(),
|
||||
DisplayItem::DefineClipScrollNode(ref mut item) => {
|
||||
let node = &clip_scroll_nodes[item.node_index.to_index()];
|
||||
let item_rect = node.clip.main;
|
||||
|
||||
let parent_spatial_id = state.spatial_ids[node.parent_index.to_index()]
|
||||
.expect("Tried to use WebRender parent SpatialId before it was defined.");
|
||||
let parent_clip_id = state.clip_ids[node.parent_index.to_index()]
|
||||
.expect("Tried to use WebRender parent ClipId before it was defined.");
|
||||
|
||||
match node.node_type {
|
||||
ClipScrollNodeType::Clip => {
|
||||
let id = builder.define_clip(
|
||||
&SpaceAndClipInfo {
|
||||
clip_id: parent_clip_id,
|
||||
spatial_id: parent_spatial_id,
|
||||
},
|
||||
item_rect,
|
||||
node.clip.complex.clone(),
|
||||
None,
|
||||
);
|
||||
|
||||
state.spatial_ids[item.node_index.to_index()] = Some(parent_spatial_id);
|
||||
state.clip_ids[item.node_index.to_index()] = Some(id);
|
||||
},
|
||||
ClipScrollNodeType::ScrollFrame(scroll_sensitivity, external_id) => {
|
||||
let space_clip_info = builder.define_scroll_frame(
|
||||
&SpaceAndClipInfo {
|
||||
clip_id: parent_clip_id,
|
||||
spatial_id: parent_spatial_id,
|
||||
},
|
||||
Some(external_id),
|
||||
node.content_rect,
|
||||
node.clip.main,
|
||||
node.clip.complex.clone(),
|
||||
None,
|
||||
scroll_sensitivity,
|
||||
webrender_api::units::LayoutVector2D::zero(),
|
||||
);
|
||||
|
||||
state.clip_ids[item.node_index.to_index()] = Some(space_clip_info.clip_id);
|
||||
state.spatial_ids[item.node_index.to_index()] =
|
||||
Some(space_clip_info.spatial_id);
|
||||
},
|
||||
ClipScrollNodeType::StickyFrame(ref sticky_data) => {
|
||||
// TODO: Add define_sticky_frame_with_parent to WebRender.
|
||||
let id = builder.define_sticky_frame(
|
||||
parent_spatial_id,
|
||||
item_rect,
|
||||
sticky_data.margins,
|
||||
sticky_data.vertical_offset_bounds,
|
||||
sticky_data.horizontal_offset_bounds,
|
||||
webrender_api::units::LayoutVector2D::zero(),
|
||||
);
|
||||
|
||||
state.spatial_ids[item.node_index.to_index()] = Some(id);
|
||||
state.clip_ids[item.node_index.to_index()] = Some(parent_clip_id);
|
||||
},
|
||||
ClipScrollNodeType::Placeholder => {
|
||||
unreachable!("Found DefineClipScrollNode for Placeholder type node.");
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_common_item_properties(
|
||||
base: &BaseDisplayItem,
|
||||
state: &ClipScrollState,
|
||||
) -> CommonItemProperties {
|
||||
let tag = match base.metadata.pointing {
|
||||
Some(cursor) => Some((base.metadata.node.0 as u64, cursor)),
|
||||
None => None,
|
||||
};
|
||||
CommonItemProperties {
|
||||
clip_rect: base.clip_rect,
|
||||
spatial_id: state.active_spatial_id,
|
||||
clip_id: state.active_clip_id,
|
||||
// TODO(gw): Make use of the WR backface visibility functionality.
|
||||
is_backface_visible: true,
|
||||
hit_info: tag,
|
||||
}
|
||||
}
|
1128
components/layout_2020/flex.rs
Normal file
1128
components/layout_2020/flex.rs
Normal file
File diff suppressed because it is too large
Load diff
605
components/layout_2020/floats.rs
Normal file
605
components/layout_2020/floats.rs
Normal file
|
@ -0,0 +1,605 @@
|
|||
/* 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::block::FormattingContextType;
|
||||
use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
|
||||
use crate::persistent_list::PersistentList;
|
||||
use app_units::{Au, MAX_AU};
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt;
|
||||
use style::computed_values::float::T as StyleFloat;
|
||||
use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
|
||||
use style::values::computed::Size;
|
||||
|
||||
/// The kind of float: left or right.
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
pub enum FloatKind {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl FloatKind {
|
||||
pub fn from_property(property: StyleFloat) -> Option<FloatKind> {
|
||||
match property {
|
||||
StyleFloat::None => None,
|
||||
StyleFloat::Left => Some(FloatKind::Left),
|
||||
StyleFloat::Right => Some(FloatKind::Right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of clearance: left, right, or both.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ClearType {
|
||||
Left,
|
||||
Right,
|
||||
Both,
|
||||
}
|
||||
|
||||
/// Information about a single float.
|
||||
#[derive(Clone, Copy)]
|
||||
struct Float {
|
||||
/// The boundaries of this float.
|
||||
bounds: LogicalRect<Au>,
|
||||
/// The kind of float: left or right.
|
||||
kind: FloatKind,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Float {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "bounds={:?} kind={:?}", self.bounds, self.kind)
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the floats next to a flow.
|
||||
#[derive(Clone)]
|
||||
struct FloatList {
|
||||
/// Information about each of the floats here.
|
||||
floats: PersistentList<Float>,
|
||||
/// Cached copy of the maximum block-start offset of the float.
|
||||
max_block_start: Option<Au>,
|
||||
}
|
||||
|
||||
impl FloatList {
|
||||
fn new() -> FloatList {
|
||||
FloatList {
|
||||
floats: PersistentList::new(),
|
||||
max_block_start: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the list is allocated and false otherwise. If false, there are guaranteed
|
||||
/// not to be any floats.
|
||||
fn is_present(&self) -> bool {
|
||||
self.floats.len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FloatList {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"max_block_start={:?} floats={}",
|
||||
self.max_block_start,
|
||||
self.floats.len()
|
||||
)?;
|
||||
for float in self.floats.iter() {
|
||||
write!(f, " {:?}", float)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// All the information necessary to place a float.
|
||||
pub struct PlacementInfo {
|
||||
/// The dimensions of the float.
|
||||
pub size: LogicalSize<Au>,
|
||||
/// The minimum block-start of the float, as determined by earlier elements.
|
||||
pub ceiling: Au,
|
||||
/// The maximum inline-end position of the float, generally determined by the containing block.
|
||||
pub max_inline_size: Au,
|
||||
/// The kind of float.
|
||||
pub kind: FloatKind,
|
||||
}
|
||||
|
||||
impl fmt::Debug for PlacementInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"size={:?} ceiling={:?} max_inline_size={:?} kind={:?}",
|
||||
self.size, self.ceiling, self.max_inline_size, self.kind
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn range_intersect(
|
||||
block_start_1: Au,
|
||||
block_end_1: Au,
|
||||
block_start_2: Au,
|
||||
block_end_2: Au,
|
||||
) -> (Au, Au) {
|
||||
(
|
||||
max(block_start_1, block_start_2),
|
||||
min(block_end_1, block_end_2),
|
||||
)
|
||||
}
|
||||
|
||||
/// Encapsulates information about floats. This is optimized to avoid allocation if there are
|
||||
/// no floats, and to avoid copying when translating the list of floats downward.
|
||||
#[derive(Clone)]
|
||||
pub struct Floats {
|
||||
/// The list of floats.
|
||||
list: FloatList,
|
||||
/// The offset of the flow relative to the first float.
|
||||
offset: LogicalSize<Au>,
|
||||
/// The writing mode of these floats.
|
||||
pub writing_mode: WritingMode,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Floats {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if !self.list.is_present() {
|
||||
write!(f, "[empty]")
|
||||
} else {
|
||||
write!(f, "offset={:?} floats={:?}", self.offset, self.list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Floats {
|
||||
/// Creates a new `Floats` object.
|
||||
pub fn new(writing_mode: WritingMode) -> Floats {
|
||||
Floats {
|
||||
list: FloatList::new(),
|
||||
offset: LogicalSize::zero(writing_mode),
|
||||
writing_mode: writing_mode,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjusts the recorded offset of the flow relative to the first float.
|
||||
pub fn translate(&mut self, delta: LogicalSize<Au>) {
|
||||
self.offset = self.offset + delta
|
||||
}
|
||||
|
||||
/// Returns the position of the last float in flow coordinates.
|
||||
pub fn last_float_pos(&self) -> Option<LogicalRect<Au>> {
|
||||
match self.list.floats.front() {
|
||||
None => None,
|
||||
Some(float) => Some(float.bounds.translate_by_size(self.offset)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a rectangle that encloses the region from block-start to block-start + block-size,
|
||||
/// with inline-size small enough that it doesn't collide with any floats. max_x is the
|
||||
/// inline-size beyond which floats have no effect. (Generally this is the containing block
|
||||
/// inline-size.)
|
||||
pub fn available_rect(
|
||||
&self,
|
||||
block_start: Au,
|
||||
block_size: Au,
|
||||
max_x: Au,
|
||||
) -> Option<LogicalRect<Au>> {
|
||||
let list = &self.list;
|
||||
let block_start = block_start - self.offset.block;
|
||||
|
||||
debug!("available_rect: trying to find space at {:?}", block_start);
|
||||
|
||||
// Relevant dimensions for the inline-end-most inline-start float
|
||||
let mut max_inline_start = Au(0) - self.offset.inline;
|
||||
let mut l_block_start = None;
|
||||
let mut l_block_end = None;
|
||||
// Relevant dimensions for the inline-start-most inline-end float
|
||||
let mut min_inline_end = max_x - self.offset.inline;
|
||||
let mut r_block_start = None;
|
||||
let mut r_block_end = None;
|
||||
|
||||
// Find the float collisions for the given range in the block direction.
|
||||
for float in list.floats.iter() {
|
||||
debug!("available_rect: Checking for collision against float");
|
||||
let float_pos = float.bounds.start;
|
||||
let float_size = float.bounds.size;
|
||||
|
||||
debug!("float_pos: {:?}, float_size: {:?}", float_pos, float_size);
|
||||
match float.kind {
|
||||
FloatKind::Left
|
||||
if float_pos.i + float_size.inline > max_inline_start &&
|
||||
float_pos.b + float_size.block > block_start &&
|
||||
float_pos.b < block_start + block_size =>
|
||||
{
|
||||
max_inline_start = float_pos.i + float_size.inline;
|
||||
|
||||
l_block_start = Some(float_pos.b);
|
||||
l_block_end = Some(float_pos.b + float_size.block);
|
||||
|
||||
debug!(
|
||||
"available_rect: collision with inline_start float: new \
|
||||
max_inline_start is {:?}",
|
||||
max_inline_start
|
||||
);
|
||||
}
|
||||
FloatKind::Right
|
||||
if float_pos.i < min_inline_end &&
|
||||
float_pos.b + float_size.block > block_start &&
|
||||
float_pos.b < block_start + block_size =>
|
||||
{
|
||||
min_inline_end = float_pos.i;
|
||||
|
||||
r_block_start = Some(float_pos.b);
|
||||
r_block_end = Some(float_pos.b + float_size.block);
|
||||
debug!(
|
||||
"available_rect: collision with inline_end float: new min_inline_end \
|
||||
is {:?}",
|
||||
min_inline_end
|
||||
);
|
||||
}
|
||||
FloatKind::Left | FloatKind::Right => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Extend the vertical range of the rectangle to the closest floats.
|
||||
// If there are floats on both sides, take the intersection of the
|
||||
// two areas. Also make sure we never return a block-start smaller than the
|
||||
// given upper bound.
|
||||
let (block_start, block_end) =
|
||||
match (r_block_start, r_block_end, l_block_start, l_block_end) {
|
||||
(
|
||||
Some(r_block_start),
|
||||
Some(r_block_end),
|
||||
Some(l_block_start),
|
||||
Some(l_block_end),
|
||||
) => range_intersect(
|
||||
max(block_start, r_block_start),
|
||||
r_block_end,
|
||||
max(block_start, l_block_start),
|
||||
l_block_end,
|
||||
),
|
||||
(None, None, Some(l_block_start), Some(l_block_end)) => {
|
||||
(max(block_start, l_block_start), l_block_end)
|
||||
},
|
||||
(Some(r_block_start), Some(r_block_end), None, None) => {
|
||||
(max(block_start, r_block_start), r_block_end)
|
||||
},
|
||||
(None, None, None, None) => return None,
|
||||
_ => panic!("Reached unreachable state when computing float area"),
|
||||
};
|
||||
|
||||
// FIXME(eatkinson): This assertion is too strong and fails in some cases. It is OK to
|
||||
// return negative inline-sizes since we check against that inline-end away, but we should
|
||||
// still understand why they occur and add a stronger assertion here.
|
||||
// assert!(max_inline-start < min_inline-end);
|
||||
|
||||
assert!(block_start <= block_end, "Float position error");
|
||||
|
||||
Some(LogicalRect::new(
|
||||
self.writing_mode,
|
||||
max_inline_start + self.offset.inline,
|
||||
block_start + self.offset.block,
|
||||
min_inline_end - max_inline_start,
|
||||
block_end - block_start,
|
||||
))
|
||||
}
|
||||
|
||||
/// Adds a new float to the list.
|
||||
pub fn add_float(&mut self, info: &PlacementInfo) {
|
||||
let new_info = PlacementInfo {
|
||||
size: info.size,
|
||||
ceiling: match self.list.max_block_start {
|
||||
None => info.ceiling,
|
||||
Some(max_block_start) => max(info.ceiling, max_block_start + self.offset.block),
|
||||
},
|
||||
max_inline_size: info.max_inline_size,
|
||||
kind: info.kind,
|
||||
};
|
||||
|
||||
debug!("add_float: added float with info {:?}", new_info);
|
||||
|
||||
let new_float = Float {
|
||||
bounds: LogicalRect::from_point_size(
|
||||
self.writing_mode,
|
||||
self.place_between_floats(&new_info).start - self.offset,
|
||||
info.size,
|
||||
),
|
||||
kind: info.kind,
|
||||
};
|
||||
|
||||
self.list.floats = self.list.floats.prepend_elem(new_float);
|
||||
self.list.max_block_start = match self.list.max_block_start {
|
||||
None => Some(new_float.bounds.start.b),
|
||||
Some(max_block_start) => Some(max(max_block_start, new_float.bounds.start.b)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the three sides of the bounding rectangle in the block-start direction, finds the
|
||||
/// largest block-size that will result in the rectangle not colliding with any floats. Returns
|
||||
/// `None` if that block-size is infinite.
|
||||
fn max_block_size_for_bounds(
|
||||
&self,
|
||||
inline_start: Au,
|
||||
block_start: Au,
|
||||
inline_size: Au,
|
||||
) -> Option<Au> {
|
||||
let list = &self.list;
|
||||
|
||||
let block_start = block_start - self.offset.block;
|
||||
let inline_start = inline_start - self.offset.inline;
|
||||
let mut max_block_size = None;
|
||||
|
||||
for float in list.floats.iter() {
|
||||
if float.bounds.start.b + float.bounds.size.block > block_start &&
|
||||
float.bounds.start.i + float.bounds.size.inline > inline_start &&
|
||||
float.bounds.start.i < inline_start + inline_size
|
||||
{
|
||||
let new_y = float.bounds.start.b;
|
||||
max_block_size = Some(min(max_block_size.unwrap_or(new_y), new_y));
|
||||
}
|
||||
}
|
||||
|
||||
max_block_size.map(|h| h + self.offset.block)
|
||||
}
|
||||
|
||||
/// Given placement information, finds the closest place a fragment can be positioned without
|
||||
/// colliding with any floats.
|
||||
pub fn place_between_floats(&self, info: &PlacementInfo) -> LogicalRect<Au> {
|
||||
debug!("place_between_floats: Placing object with {:?}", info.size);
|
||||
|
||||
// If no floats, use this fast path.
|
||||
if !self.list.is_present() {
|
||||
match info.kind {
|
||||
FloatKind::Left => {
|
||||
return LogicalRect::new(
|
||||
self.writing_mode,
|
||||
Au(0),
|
||||
info.ceiling,
|
||||
info.max_inline_size,
|
||||
MAX_AU,
|
||||
);
|
||||
},
|
||||
FloatKind::Right => {
|
||||
return LogicalRect::new(
|
||||
self.writing_mode,
|
||||
info.max_inline_size - info.size.inline,
|
||||
info.ceiling,
|
||||
info.max_inline_size,
|
||||
MAX_AU,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Can't go any higher than previous floats or previous elements in the document.
|
||||
let mut float_b = info.ceiling;
|
||||
loop {
|
||||
let maybe_location =
|
||||
self.available_rect(float_b, info.size.block, info.max_inline_size);
|
||||
debug!(
|
||||
"place_float: got available rect: {:?} for block-pos: {:?}",
|
||||
maybe_location, float_b
|
||||
);
|
||||
match maybe_location {
|
||||
// If there are no floats blocking us, return the current location
|
||||
// TODO(eatkinson): integrate with overflow
|
||||
None => {
|
||||
return match info.kind {
|
||||
FloatKind::Left => LogicalRect::new(
|
||||
self.writing_mode,
|
||||
Au(0),
|
||||
float_b,
|
||||
info.max_inline_size,
|
||||
MAX_AU,
|
||||
),
|
||||
FloatKind::Right => LogicalRect::new(
|
||||
self.writing_mode,
|
||||
info.max_inline_size - info.size.inline,
|
||||
float_b,
|
||||
info.max_inline_size,
|
||||
MAX_AU,
|
||||
),
|
||||
};
|
||||
},
|
||||
Some(rect) => {
|
||||
assert_ne!(
|
||||
rect.start.b + rect.size.block,
|
||||
float_b,
|
||||
"Non-terminating float placement"
|
||||
);
|
||||
|
||||
// Place here if there is enough room
|
||||
if rect.size.inline >= info.size.inline {
|
||||
let block_size = self.max_block_size_for_bounds(
|
||||
rect.start.i,
|
||||
rect.start.b,
|
||||
rect.size.inline,
|
||||
);
|
||||
let block_size = block_size.unwrap_or(MAX_AU);
|
||||
return match info.kind {
|
||||
FloatKind::Left => LogicalRect::new(
|
||||
self.writing_mode,
|
||||
rect.start.i,
|
||||
float_b,
|
||||
rect.size.inline,
|
||||
block_size,
|
||||
),
|
||||
FloatKind::Right => LogicalRect::new(
|
||||
self.writing_mode,
|
||||
rect.start.i + rect.size.inline - info.size.inline,
|
||||
float_b,
|
||||
rect.size.inline,
|
||||
block_size,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// Try to place at the next-lowest location.
|
||||
// Need to be careful of fencepost errors.
|
||||
float_b = rect.start.b + rect.size.block;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clearance(&self, clear: ClearType) -> Au {
|
||||
let list = &self.list;
|
||||
let mut clearance = Au(0);
|
||||
for float in list.floats.iter() {
|
||||
match (clear, float.kind) {
|
||||
(ClearType::Left, FloatKind::Left) |
|
||||
(ClearType::Right, FloatKind::Right) |
|
||||
(ClearType::Both, _) => {
|
||||
let b = self.offset.block + float.bounds.start.b + float.bounds.size.block;
|
||||
clearance = max(clearance, b);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
clearance
|
||||
}
|
||||
|
||||
pub fn is_present(&self) -> bool {
|
||||
self.list.is_present()
|
||||
}
|
||||
}
|
||||
|
||||
/// The speculated inline sizes of floats flowing through or around a flow (depending on whether
|
||||
/// the flow is a block formatting context). These speculations are always *upper bounds*; the
|
||||
/// actual inline sizes might be less. Note that this implies that a speculated value of zero is a
|
||||
/// guarantee that there will be no floats on that side.
|
||||
///
|
||||
/// This is used for two purposes: (a) determining whether we can lay out blocks in parallel; (b)
|
||||
/// guessing the inline-sizes of block formatting contexts in an effort to lay them out in
|
||||
/// parallel.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SpeculatedFloatPlacement {
|
||||
/// The estimated inline size (an upper bound) of the left floats flowing through this flow.
|
||||
pub left: Au,
|
||||
/// The estimated inline size (an upper bound) of the right floats flowing through this flow.
|
||||
pub right: Au,
|
||||
}
|
||||
|
||||
impl fmt::Debug for SpeculatedFloatPlacement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "L {:?} R {:?}", self.left, self.right)
|
||||
}
|
||||
}
|
||||
|
||||
impl SpeculatedFloatPlacement {
|
||||
/// Returns a `SpeculatedFloatPlacement` objects with both left and right speculated inline
|
||||
/// sizes initialized to zero.
|
||||
pub fn zero() -> SpeculatedFloatPlacement {
|
||||
SpeculatedFloatPlacement {
|
||||
left: Au(0),
|
||||
right: Au(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the speculated inline size of the floats out for the inorder predecessor of this
|
||||
/// flow, computes the speculated inline size of the floats flowing in.
|
||||
pub fn compute_floats_in(&mut self, flow: &mut dyn Flow) {
|
||||
let base_flow = flow.base();
|
||||
if base_flow.flags.contains(FlowFlags::CLEARS_LEFT) {
|
||||
self.left = Au(0)
|
||||
}
|
||||
if base_flow.flags.contains(FlowFlags::CLEARS_RIGHT) {
|
||||
self.right = Au(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the speculated inline size of the floats out for this flow's last child, computes the
|
||||
/// speculated inline size of the floats out for this flow.
|
||||
pub fn compute_floats_out(&mut self, flow: &mut dyn Flow) {
|
||||
if flow.is_block_like() {
|
||||
let block_flow = flow.as_block();
|
||||
if block_flow.formatting_context_type() != FormattingContextType::None {
|
||||
*self = block_flow.base.speculated_float_placement_in;
|
||||
} else {
|
||||
if self.left > Au(0) || self.right > Au(0) {
|
||||
let speculated_inline_content_edge_offsets =
|
||||
block_flow.fragment.guess_inline_content_edge_offsets();
|
||||
if self.left > Au(0) && speculated_inline_content_edge_offsets.start > Au(0) {
|
||||
self.left = self.left + speculated_inline_content_edge_offsets.start
|
||||
}
|
||||
if self.right > Au(0) && speculated_inline_content_edge_offsets.end > Au(0) {
|
||||
self.right = self.right + speculated_inline_content_edge_offsets.end
|
||||
}
|
||||
}
|
||||
|
||||
self.left = max(
|
||||
self.left,
|
||||
block_flow.base.speculated_float_placement_in.left,
|
||||
);
|
||||
self.right = max(
|
||||
self.right,
|
||||
block_flow.base.speculated_float_placement_in.right,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let base_flow = flow.base();
|
||||
if !base_flow.flags.is_float() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut float_inline_size = base_flow.intrinsic_inline_sizes.preferred_inline_size;
|
||||
if float_inline_size == Au(0) {
|
||||
if flow.is_block_like() {
|
||||
// Hack: If the size of the float is not fixed, then there's no
|
||||
// way we can guess at its size now. So just pick an arbitrary
|
||||
// nonzero value (in this case, 1px) so that the layout
|
||||
// traversal logic will know that objects later in the document
|
||||
// might flow around this float.
|
||||
let inline_size = flow.as_block().fragment.style.content_inline_size();
|
||||
let fixed = match inline_size {
|
||||
Size::Auto => false,
|
||||
Size::LengthPercentage(ref lp) => {
|
||||
lp.0.is_definitely_zero() || lp.0.maybe_to_used_value(None).is_some()
|
||||
},
|
||||
};
|
||||
if !fixed {
|
||||
float_inline_size = Au::from_px(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match base_flow.flags.float_kind() {
|
||||
StyleFloat::None => {},
|
||||
StyleFloat::Left => self.left = self.left + float_inline_size,
|
||||
StyleFloat::Right => self.right = self.right + float_inline_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a flow, computes the speculated inline size of the floats in of its first child.
|
||||
pub fn compute_floats_in_for_first_child(
|
||||
parent_flow: &mut dyn Flow,
|
||||
) -> SpeculatedFloatPlacement {
|
||||
if !parent_flow.is_block_like() {
|
||||
return parent_flow.base().speculated_float_placement_in;
|
||||
}
|
||||
|
||||
let parent_block_flow = parent_flow.as_block();
|
||||
if parent_block_flow.formatting_context_type() != FormattingContextType::None {
|
||||
return SpeculatedFloatPlacement::zero();
|
||||
}
|
||||
|
||||
let mut placement = parent_block_flow.base.speculated_float_placement_in;
|
||||
let speculated_inline_content_edge_offsets = parent_block_flow
|
||||
.fragment
|
||||
.guess_inline_content_edge_offsets();
|
||||
|
||||
if speculated_inline_content_edge_offsets.start > Au(0) {
|
||||
placement.left = if placement.left > speculated_inline_content_edge_offsets.start {
|
||||
placement.left - speculated_inline_content_edge_offsets.start
|
||||
} else {
|
||||
Au(0)
|
||||
}
|
||||
}
|
||||
if speculated_inline_content_edge_offsets.end > Au(0) {
|
||||
placement.right = if placement.right > speculated_inline_content_edge_offsets.end {
|
||||
placement.right - speculated_inline_content_edge_offsets.end
|
||||
} else {
|
||||
Au(0)
|
||||
}
|
||||
}
|
||||
|
||||
placement
|
||||
}
|
||||
}
|
1458
components/layout_2020/flow.rs
Normal file
1458
components/layout_2020/flow.rs
Normal file
File diff suppressed because it is too large
Load diff
197
components/layout_2020/flow_list.rs
Normal file
197
components/layout_2020/flow_list.rs
Normal file
|
@ -0,0 +1,197 @@
|
|||
/* 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::{Flow, FlowClass};
|
||||
use crate::flow_ref::FlowRef;
|
||||
use serde::ser::{Serialize, SerializeSeq, Serializer};
|
||||
use serde_json::{to_value, Map, Value};
|
||||
use std::collections::{linked_list, LinkedList};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// This needs to be reworked now that we have dynamically-sized types in Rust.
|
||||
/// Until then, it's just a wrapper around LinkedList.
|
||||
///
|
||||
/// SECURITY-NOTE(pcwalton): It is very important that `FlowRef` values not leak directly to
|
||||
/// layout. Layout code must only interact with `&Flow` or `&mut Flow` values. Otherwise, layout
|
||||
/// could stash `FlowRef` values in random places unknown to the system and thereby cause data
|
||||
/// races. Those data races can lead to memory safety problems, potentially including arbitrary
|
||||
/// remote code execution! In general, do not add new methods to this file (e.g. new ways of
|
||||
/// iterating over flows) unless you are *very* sure of what you are doing.
|
||||
pub struct FlowList {
|
||||
flows: LinkedList<FlowRef>,
|
||||
}
|
||||
|
||||
impl Serialize for FlowList {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut serializer = serializer.serialize_seq(Some(self.len()))?;
|
||||
for f in self.iter() {
|
||||
let mut flow_val = Map::new();
|
||||
flow_val.insert("class".to_owned(), to_value(f.class()).unwrap());
|
||||
let data = match f.class() {
|
||||
FlowClass::Block => to_value(f.as_block()).unwrap(),
|
||||
FlowClass::Inline => to_value(f.as_inline()).unwrap(),
|
||||
FlowClass::Table => to_value(f.as_table()).unwrap(),
|
||||
FlowClass::TableWrapper => to_value(f.as_table_wrapper()).unwrap(),
|
||||
FlowClass::TableRowGroup => to_value(f.as_table_rowgroup()).unwrap(),
|
||||
FlowClass::TableRow => to_value(f.as_table_row()).unwrap(),
|
||||
FlowClass::TableCell => to_value(f.as_table_cell()).unwrap(),
|
||||
FlowClass::Flex => to_value(f.as_flex()).unwrap(),
|
||||
FlowClass::ListItem |
|
||||
FlowClass::TableColGroup |
|
||||
FlowClass::TableCaption |
|
||||
FlowClass::Multicol |
|
||||
FlowClass::MulticolColumn => {
|
||||
Value::Null // Not implemented yet
|
||||
},
|
||||
};
|
||||
flow_val.insert("data".to_owned(), data);
|
||||
serializer.serialize_element(&flow_val)?;
|
||||
}
|
||||
serializer.end()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MutFlowListIterator<'a> {
|
||||
it: linked_list::IterMut<'a, FlowRef>,
|
||||
}
|
||||
|
||||
pub struct FlowListIterator<'a> {
|
||||
it: linked_list::Iter<'a, FlowRef>,
|
||||
}
|
||||
|
||||
impl FlowList {
|
||||
/// Add an element last in the list
|
||||
///
|
||||
/// O(1)
|
||||
pub fn push_back(&mut self, new_tail: FlowRef) {
|
||||
self.flows.push_back(new_tail);
|
||||
}
|
||||
|
||||
pub fn push_back_arc(&mut self, new_head: Arc<dyn Flow>) {
|
||||
self.flows.push_back(FlowRef::new(new_head));
|
||||
}
|
||||
|
||||
pub fn push_front_arc(&mut self, new_head: Arc<dyn Flow>) {
|
||||
self.flows.push_front(FlowRef::new(new_head));
|
||||
}
|
||||
|
||||
pub fn pop_front_arc(&mut self) -> Option<Arc<dyn Flow>> {
|
||||
self.flows.pop_front().map(FlowRef::into_arc)
|
||||
}
|
||||
|
||||
/// Create an empty list
|
||||
#[inline]
|
||||
pub fn new() -> FlowList {
|
||||
FlowList {
|
||||
flows: LinkedList::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide a forward iterator.
|
||||
///
|
||||
/// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method
|
||||
/// to do so! See the comment above in `FlowList`.
|
||||
#[inline]
|
||||
pub fn iter<'a>(&'a self) -> FlowListIterator {
|
||||
FlowListIterator {
|
||||
it: self.flows.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide a forward iterator with mutable references
|
||||
///
|
||||
/// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method
|
||||
/// to do so! See the comment above in `FlowList`.
|
||||
#[inline]
|
||||
pub fn iter_mut(&mut self) -> MutFlowListIterator {
|
||||
MutFlowListIterator {
|
||||
it: self.flows.iter_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides a caching random-access iterator that yields mutable references. This is
|
||||
/// guaranteed to perform no more than O(n) pointer chases.
|
||||
///
|
||||
/// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method
|
||||
/// to do so! See the comment above in `FlowList`.
|
||||
#[inline]
|
||||
pub fn random_access_mut(&mut self) -> FlowListRandomAccessMut {
|
||||
let length = self.flows.len();
|
||||
FlowListRandomAccessMut {
|
||||
iterator: self.flows.iter_mut(),
|
||||
cache: Vec::with_capacity(length),
|
||||
}
|
||||
}
|
||||
|
||||
/// O(1)
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.flows.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn split_off(&mut self, i: usize) -> Self {
|
||||
FlowList {
|
||||
flows: self.flows.split_off(i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for FlowListIterator<'a> {
|
||||
fn next_back(&mut self) -> Option<&'a dyn Flow> {
|
||||
self.it.next_back().map(Deref::deref)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for MutFlowListIterator<'a> {
|
||||
fn next_back(&mut self) -> Option<&'a mut dyn Flow> {
|
||||
self.it.next_back().map(FlowRef::deref_mut)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FlowListIterator<'a> {
|
||||
type Item = &'a dyn Flow;
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<&'a dyn Flow> {
|
||||
self.it.next().map(Deref::deref)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.it.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for MutFlowListIterator<'a> {
|
||||
type Item = &'a mut dyn Flow;
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<&'a mut dyn Flow> {
|
||||
self.it.next().map(FlowRef::deref_mut)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.it.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
/// A caching random-access iterator that yields mutable references. This is guaranteed to perform
|
||||
/// no more than O(n) pointer chases.
|
||||
pub struct FlowListRandomAccessMut<'a> {
|
||||
iterator: linked_list::IterMut<'a, FlowRef>,
|
||||
cache: Vec<FlowRef>,
|
||||
}
|
||||
|
||||
impl<'a> FlowListRandomAccessMut<'a> {
|
||||
pub fn get<'b>(&'b mut self, index: usize) -> &'b mut dyn Flow {
|
||||
while index >= self.cache.len() {
|
||||
match self.iterator.next() {
|
||||
None => panic!("Flow index out of range!"),
|
||||
Some(next_flow) => self.cache.push((*next_flow).clone()),
|
||||
}
|
||||
}
|
||||
FlowRef::deref_mut(&mut self.cache[index])
|
||||
}
|
||||
}
|
64
components/layout_2020/flow_ref.rs
Normal file
64
components/layout_2020/flow_ref.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Reference-counted pointers to flows.
|
||||
//!
|
||||
//! Eventually, with dynamically sized types in Rust, much of this code will
|
||||
//! be superfluous. This design is largely duplicating logic of Arc<T> and
|
||||
//! Weak<T>; please see comments there for details.
|
||||
|
||||
use crate::flow::Flow;
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FlowRef(Arc<dyn Flow>);
|
||||
|
||||
impl Deref for FlowRef {
|
||||
type Target = dyn Flow;
|
||||
fn deref(&self) -> &dyn Flow {
|
||||
self.0.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowRef {
|
||||
/// `FlowRef`s can only be made available to the traversal code.
|
||||
/// See https://github.com/servo/servo/issues/14014 for more details.
|
||||
pub fn new(mut r: Arc<dyn Flow>) -> Self {
|
||||
// This assertion checks that this `FlowRef` does not alias normal `Arc`s.
|
||||
// If that happens, we're in trouble.
|
||||
assert!(Arc::get_mut(&mut r).is_some());
|
||||
FlowRef(r)
|
||||
}
|
||||
pub fn get_mut(this: &mut FlowRef) -> Option<&mut dyn Flow> {
|
||||
Arc::get_mut(&mut this.0)
|
||||
}
|
||||
pub fn downgrade(this: &FlowRef) -> WeakFlowRef {
|
||||
WeakFlowRef(Arc::downgrade(&this.0))
|
||||
}
|
||||
pub fn into_arc(mut this: FlowRef) -> Arc<dyn Flow> {
|
||||
// This assertion checks that this `FlowRef` does not alias normal `Arc`s.
|
||||
// If that happens, we're in trouble.
|
||||
assert!(FlowRef::get_mut(&mut this).is_some());
|
||||
this.0
|
||||
}
|
||||
/// WARNING: This should only be used when there is no aliasing:
|
||||
/// when the traversal ensures that no other threads accesses the same flow at the same time.
|
||||
/// See https://github.com/servo/servo/issues/6503
|
||||
/// Use Arc::get_mut instead when possible (e.g. on an Arc that was just created).
|
||||
#[allow(unsafe_code)]
|
||||
pub fn deref_mut(this: &mut FlowRef) -> &mut dyn Flow {
|
||||
let ptr: *const dyn Flow = &*this.0;
|
||||
unsafe { &mut *(ptr as *mut dyn Flow) }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WeakFlowRef(Weak<dyn Flow>);
|
||||
|
||||
impl WeakFlowRef {
|
||||
pub fn upgrade(&self) -> Option<FlowRef> {
|
||||
self.0.upgrade().map(FlowRef)
|
||||
}
|
||||
}
|
3455
components/layout_2020/fragment.rs
Normal file
3455
components/layout_2020/fragment.rs
Normal file
File diff suppressed because it is too large
Load diff
651
components/layout_2020/generated_content.rs
Normal file
651
components/layout_2020/generated_content.rs
Normal file
|
@ -0,0 +1,651 @@
|
|||
/* 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 generated content assignment phase.
|
||||
//!
|
||||
//! This phase handles CSS counters, quotes, and ordered lists per CSS § 12.3-12.5. It cannot be
|
||||
//! done in parallel and is therefore a sequential pass that runs on as little of the flow tree
|
||||
//! as possible.
|
||||
|
||||
use crate::context::{with_thread_local_font_context, LayoutContext};
|
||||
use crate::display_list::items::OpaqueNode;
|
||||
use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
|
||||
use crate::fragment::{
|
||||
Fragment, GeneratedContentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo,
|
||||
};
|
||||
use crate::text::TextRunScanner;
|
||||
use crate::traversal::InorderFlowTraversal;
|
||||
use script_layout_interface::wrapper_traits::PseudoElementType;
|
||||
use smallvec::SmallVec;
|
||||
use std::collections::{HashMap, LinkedList};
|
||||
use style::computed_values::display::T as Display;
|
||||
use style::computed_values::list_style_type::T as ListStyleType;
|
||||
use style::properties::ComputedValues;
|
||||
use style::selector_parser::RestyleDamage;
|
||||
use style::servo::restyle_damage::ServoRestyleDamage;
|
||||
use style::values::generics::counters::ContentItem;
|
||||
|
||||
// Decimal styles per CSS-COUNTER-STYLES § 6.1:
|
||||
static DECIMAL: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
// TODO(pcwalton): `decimal-leading-zero`
|
||||
static ARABIC_INDIC: [char; 10] = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];
|
||||
// TODO(pcwalton): `armenian`, `upper-armenian`, `lower-armenian`
|
||||
static BENGALI: [char; 10] = [
|
||||
'০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯',
|
||||
];
|
||||
static CAMBODIAN: [char; 10] = [
|
||||
'០', '១', '២', '៣', '៤', '៥', '៦', '៧', '៨', '៩',
|
||||
];
|
||||
// TODO(pcwalton): Suffix for CJK decimal.
|
||||
static CJK_DECIMAL: [char; 10] = [
|
||||
'〇', '一', '二', '三', '四', '五', '六', '七', '八', '九',
|
||||
];
|
||||
static DEVANAGARI: [char; 10] = [
|
||||
'०', '१', '२', '३', '४', '५', '६', '७', '८', '९',
|
||||
];
|
||||
// TODO(pcwalton): `georgian`
|
||||
static GUJARATI: [char; 10] = [
|
||||
'૦', '૧', '૨', '૩', '૪', '૫', '૬', '૭', '૮', '૯',
|
||||
];
|
||||
static GURMUKHI: [char; 10] = [
|
||||
'੦', '੧', '੨', '੩', '੪', '੫', '੬', '੭', '੮', '੯',
|
||||
];
|
||||
// TODO(pcwalton): `hebrew`
|
||||
static KANNADA: [char; 10] = [
|
||||
'೦', '೧', '೨', '೩', '೪', '೫', '೬', '೭', '೮', '೯',
|
||||
];
|
||||
static LAO: [char; 10] = [
|
||||
'໐', '໑', '໒', '໓', '໔', '໕', '໖', '໗', '໘', '໙',
|
||||
];
|
||||
static MALAYALAM: [char; 10] = [
|
||||
'൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯',
|
||||
];
|
||||
static MONGOLIAN: [char; 10] = [
|
||||
'᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙',
|
||||
];
|
||||
static MYANMAR: [char; 10] = [
|
||||
'၀', '၁', '၂', '၃', '၄', '၅', '၆', '၇', '၈', '၉',
|
||||
];
|
||||
static ORIYA: [char; 10] = [
|
||||
'୦', '୧', '୨', '୩', '୪', '୫', '୬', '୭', '୮', '୯',
|
||||
];
|
||||
static PERSIAN: [char; 10] = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
|
||||
// TODO(pcwalton): `lower-roman`, `upper-roman`
|
||||
static TELUGU: [char; 10] = [
|
||||
'౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯',
|
||||
];
|
||||
static THAI: [char; 10] = [
|
||||
'๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙',
|
||||
];
|
||||
static TIBETAN: [char; 10] = [
|
||||
'༠', '༡', '༢', '༣', '༤', '༥', '༦', '༧', '༨', '༩',
|
||||
];
|
||||
|
||||
// Alphabetic styles per CSS-COUNTER-STYLES § 6.2:
|
||||
static LOWER_ALPHA: [char; 26] = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
|
||||
't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
];
|
||||
static UPPER_ALPHA: [char; 26] = [
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
|
||||
'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
];
|
||||
static CJK_EARTHLY_BRANCH: [char; 12] = [
|
||||
'子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥',
|
||||
];
|
||||
static CJK_HEAVENLY_STEM: [char; 10] = [
|
||||
'甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸',
|
||||
];
|
||||
static LOWER_GREEK: [char; 24] = [
|
||||
'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π',
|
||||
'ρ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω',
|
||||
];
|
||||
static HIRAGANA: [char; 48] = [
|
||||
'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し', 'す',
|
||||
'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', 'の', 'は',
|
||||
'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み', 'む', 'め', 'も', 'や', 'ゆ', 'よ', 'ら',
|
||||
'り', 'る', 'れ', 'ろ', 'わ', 'ゐ', 'ゑ', 'を', 'ん',
|
||||
];
|
||||
static HIRAGANA_IROHA: [char; 47] = [
|
||||
'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を', 'わ',
|
||||
'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら', 'む', 'う', 'ゐ', 'の',
|
||||
'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ', 'え', 'て', 'あ', 'さ', 'き', 'ゆ',
|
||||
'め', 'み', 'し', 'ゑ', 'ひ', 'も', 'せ', 'す',
|
||||
];
|
||||
static KATAKANA: [char; 48] = [
|
||||
'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス',
|
||||
'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', 'ノ', 'ハ',
|
||||
'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム', 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ',
|
||||
'リ', 'ル', 'レ', 'ロ', 'ワ', 'ヰ', 'ヱ', 'ヲ', 'ン',
|
||||
];
|
||||
static KATAKANA_IROHA: [char; 47] = [
|
||||
'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', 'ヲ', 'ワ',
|
||||
'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', 'ム', 'ウ', 'ヰ', 'ノ',
|
||||
'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ', 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ',
|
||||
'メ', 'ミ', 'シ', 'ヱ', 'ヒ', 'モ', 'セ', 'ス',
|
||||
];
|
||||
|
||||
/// The generated content resolution traversal.
|
||||
pub struct ResolveGeneratedContent<'a> {
|
||||
/// The layout context.
|
||||
layout_context: &'a LayoutContext<'a>,
|
||||
/// The counter representing an ordered list item.
|
||||
list_item: Counter,
|
||||
/// Named CSS counters.
|
||||
counters: HashMap<String, Counter>,
|
||||
/// The level of quote nesting.
|
||||
quote: u32,
|
||||
}
|
||||
|
||||
impl<'a> ResolveGeneratedContent<'a> {
|
||||
/// Creates a new generated content resolution traversal.
|
||||
pub fn new(layout_context: &'a LayoutContext) -> ResolveGeneratedContent<'a> {
|
||||
ResolveGeneratedContent {
|
||||
layout_context: layout_context,
|
||||
list_item: Counter::new(),
|
||||
counters: HashMap::new(),
|
||||
quote: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> InorderFlowTraversal for ResolveGeneratedContent<'a> {
|
||||
#[inline]
|
||||
fn process(&mut self, flow: &mut dyn Flow, level: u32) {
|
||||
let mut mutator = ResolveGeneratedContentFragmentMutator {
|
||||
traversal: self,
|
||||
level: level,
|
||||
is_block: flow.is_block_like(),
|
||||
incremented: false,
|
||||
};
|
||||
flow.mutate_fragments(&mut |fragment| mutator.mutate_fragment(fragment))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn should_process_subtree(&mut self, flow: &mut dyn Flow) -> bool {
|
||||
flow.base()
|
||||
.restyle_damage
|
||||
.intersects(ServoRestyleDamage::RESOLVE_GENERATED_CONTENT) ||
|
||||
flow.base().flags.intersects(
|
||||
FlowFlags::AFFECTS_COUNTERS | FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The object that mutates the generated content fragments.
|
||||
struct ResolveGeneratedContentFragmentMutator<'a, 'b: 'a> {
|
||||
/// The traversal.
|
||||
traversal: &'a mut ResolveGeneratedContent<'b>,
|
||||
/// The level we're at in the flow tree.
|
||||
level: u32,
|
||||
/// Whether this flow is a block flow.
|
||||
is_block: bool,
|
||||
/// Whether we've incremented the counter yet.
|
||||
incremented: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'b> ResolveGeneratedContentFragmentMutator<'a, 'b> {
|
||||
fn mutate_fragment(&mut self, fragment: &mut Fragment) {
|
||||
// We only reset and/or increment counters once per flow. This avoids double-incrementing
|
||||
// counters on list items (once for the main fragment and once for the marker).
|
||||
if !self.incremented {
|
||||
self.reset_and_increment_counters_as_necessary(fragment);
|
||||
}
|
||||
|
||||
let mut list_style_type = fragment.style().get_list().list_style_type;
|
||||
if fragment.style().get_box().display != Display::ListItem {
|
||||
list_style_type = ListStyleType::None
|
||||
}
|
||||
|
||||
let mut new_info = None;
|
||||
{
|
||||
let info =
|
||||
if let SpecificFragmentInfo::GeneratedContent(ref mut info) = fragment.specific {
|
||||
info
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
match **info {
|
||||
GeneratedContentInfo::ListItem => {
|
||||
new_info = self.traversal.list_item.render(
|
||||
self.traversal.layout_context,
|
||||
fragment.node,
|
||||
fragment.pseudo.clone(),
|
||||
fragment.style.clone(),
|
||||
list_style_type,
|
||||
RenderingMode::Suffix(".\u{00a0}"),
|
||||
)
|
||||
},
|
||||
GeneratedContentInfo::Empty |
|
||||
GeneratedContentInfo::ContentItem(ContentItem::String(_)) => {
|
||||
// Nothing to do here.
|
||||
},
|
||||
GeneratedContentInfo::ContentItem(ContentItem::Counter(
|
||||
ref counter_name,
|
||||
counter_style,
|
||||
)) => {
|
||||
let temporary_counter = Counter::new();
|
||||
let counter = self
|
||||
.traversal
|
||||
.counters
|
||||
.get(&*counter_name.0)
|
||||
.unwrap_or(&temporary_counter);
|
||||
new_info = counter.render(
|
||||
self.traversal.layout_context,
|
||||
fragment.node,
|
||||
fragment.pseudo.clone(),
|
||||
fragment.style.clone(),
|
||||
counter_style,
|
||||
RenderingMode::Plain,
|
||||
)
|
||||
},
|
||||
GeneratedContentInfo::ContentItem(ContentItem::Counters(
|
||||
ref counter_name,
|
||||
ref separator,
|
||||
counter_style,
|
||||
)) => {
|
||||
let temporary_counter = Counter::new();
|
||||
let counter = self
|
||||
.traversal
|
||||
.counters
|
||||
.get(&*counter_name.0)
|
||||
.unwrap_or(&temporary_counter);
|
||||
new_info = counter.render(
|
||||
self.traversal.layout_context,
|
||||
fragment.node,
|
||||
fragment.pseudo,
|
||||
fragment.style.clone(),
|
||||
counter_style,
|
||||
RenderingMode::All(&separator),
|
||||
);
|
||||
},
|
||||
GeneratedContentInfo::ContentItem(ContentItem::OpenQuote) => {
|
||||
new_info = render_text(
|
||||
self.traversal.layout_context,
|
||||
fragment.node,
|
||||
fragment.pseudo,
|
||||
fragment.style.clone(),
|
||||
self.quote(&*fragment.style, false),
|
||||
);
|
||||
self.traversal.quote += 1
|
||||
},
|
||||
GeneratedContentInfo::ContentItem(ContentItem::CloseQuote) => {
|
||||
if self.traversal.quote >= 1 {
|
||||
self.traversal.quote -= 1
|
||||
}
|
||||
|
||||
new_info = render_text(
|
||||
self.traversal.layout_context,
|
||||
fragment.node,
|
||||
fragment.pseudo,
|
||||
fragment.style.clone(),
|
||||
self.quote(&*fragment.style, true),
|
||||
);
|
||||
},
|
||||
GeneratedContentInfo::ContentItem(ContentItem::NoOpenQuote) => {
|
||||
self.traversal.quote += 1
|
||||
},
|
||||
GeneratedContentInfo::ContentItem(ContentItem::NoCloseQuote) => {
|
||||
if self.traversal.quote >= 1 {
|
||||
self.traversal.quote -= 1
|
||||
}
|
||||
},
|
||||
GeneratedContentInfo::ContentItem(ContentItem::Url(..)) => {
|
||||
unreachable!("Servo doesn't parse content: url(..) yet")
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
fragment.specific = match new_info {
|
||||
Some(new_info) => new_info,
|
||||
// If the fragment did not generate any content, replace it with a no-op placeholder
|
||||
// so that it isn't processed again on the next layout. FIXME (mbrubeck): When
|
||||
// processing an inline flow, this traversal should be allowed to insert or remove
|
||||
// fragments. Then we can just remove these fragments rather than adding placeholders.
|
||||
None => SpecificFragmentInfo::GeneratedContent(Box::new(GeneratedContentInfo::Empty)),
|
||||
};
|
||||
}
|
||||
|
||||
fn reset_and_increment_counters_as_necessary(&mut self, fragment: &mut Fragment) {
|
||||
let mut list_style_type = fragment.style().get_list().list_style_type;
|
||||
if !self.is_block || fragment.style().get_box().display != Display::ListItem {
|
||||
list_style_type = ListStyleType::None
|
||||
}
|
||||
|
||||
match list_style_type {
|
||||
ListStyleType::Disc |
|
||||
ListStyleType::None |
|
||||
ListStyleType::Circle |
|
||||
ListStyleType::Square |
|
||||
ListStyleType::DisclosureOpen |
|
||||
ListStyleType::DisclosureClosed => {},
|
||||
_ => self.traversal.list_item.increment(self.level, 1),
|
||||
}
|
||||
|
||||
// Truncate down counters.
|
||||
for (_, counter) in &mut self.traversal.counters {
|
||||
counter.truncate_to_level(self.level);
|
||||
}
|
||||
self.traversal.list_item.truncate_to_level(self.level);
|
||||
|
||||
for pair in &*fragment.style().get_counters().counter_reset {
|
||||
let counter_name = &*pair.name.0;
|
||||
if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
|
||||
counter.reset(self.level, pair.value);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut counter = Counter::new();
|
||||
counter.reset(self.level, pair.value);
|
||||
self.traversal
|
||||
.counters
|
||||
.insert(counter_name.to_owned(), counter);
|
||||
}
|
||||
|
||||
for pair in &*fragment.style().get_counters().counter_increment {
|
||||
let counter_name = &*pair.name.0;
|
||||
if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
|
||||
counter.increment(self.level, pair.value);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut counter = Counter::new();
|
||||
counter.increment(self.level, pair.value);
|
||||
self.traversal
|
||||
.counters
|
||||
.insert(counter_name.to_owned(), counter);
|
||||
}
|
||||
|
||||
self.incremented = true
|
||||
}
|
||||
|
||||
fn quote(&self, style: &ComputedValues, close: bool) -> String {
|
||||
let quotes = &style.get_list().quotes;
|
||||
if quotes.0.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
let pair = if self.traversal.quote as usize >= quotes.0.len() {
|
||||
quotes.0.last().unwrap()
|
||||
} else {
|
||||
"es.0[self.traversal.quote as usize]
|
||||
};
|
||||
if close {
|
||||
pair.closing.to_string()
|
||||
} else {
|
||||
pair.opening.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A counter per CSS 2.1 § 12.4.
|
||||
struct Counter {
|
||||
/// The values at each level.
|
||||
values: Vec<CounterValue>,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
fn new() -> Counter {
|
||||
Counter { values: Vec::new() }
|
||||
}
|
||||
|
||||
fn reset(&mut self, level: u32, value: i32) {
|
||||
// Do we have an instance of the counter at this level? If so, just mutate it.
|
||||
if let Some(ref mut existing_value) = self.values.last_mut() {
|
||||
if level == existing_value.level {
|
||||
existing_value.value = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, push a new instance of the counter.
|
||||
self.values.push(CounterValue {
|
||||
level: level,
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
|
||||
fn truncate_to_level(&mut self, level: u32) {
|
||||
if let Some(position) = self.values.iter().position(|value| value.level > level) {
|
||||
self.values.truncate(position)
|
||||
}
|
||||
}
|
||||
|
||||
fn increment(&mut self, level: u32, amount: i32) {
|
||||
if let Some(ref mut value) = self.values.last_mut() {
|
||||
value.value += amount;
|
||||
return;
|
||||
}
|
||||
|
||||
self.values.push(CounterValue {
|
||||
level: level,
|
||||
value: amount,
|
||||
})
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
layout_context: &LayoutContext,
|
||||
node: OpaqueNode,
|
||||
pseudo: PseudoElementType,
|
||||
style: crate::ServoArc<ComputedValues>,
|
||||
list_style_type: ListStyleType,
|
||||
mode: RenderingMode,
|
||||
) -> Option<SpecificFragmentInfo> {
|
||||
let mut string = String::new();
|
||||
match mode {
|
||||
RenderingMode::Plain => {
|
||||
let value = match self.values.last() {
|
||||
Some(ref value) => value.value,
|
||||
None => 0,
|
||||
};
|
||||
push_representation(value, list_style_type, &mut string)
|
||||
},
|
||||
RenderingMode::Suffix(suffix) => {
|
||||
let value = match self.values.last() {
|
||||
Some(ref value) => value.value,
|
||||
None => 0,
|
||||
};
|
||||
push_representation(value, list_style_type, &mut string);
|
||||
string.push_str(suffix)
|
||||
},
|
||||
RenderingMode::All(separator) => {
|
||||
let mut first = true;
|
||||
for value in &self.values {
|
||||
if !first {
|
||||
string.push_str(separator)
|
||||
}
|
||||
first = false;
|
||||
push_representation(value.value, list_style_type, &mut string)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if string.is_empty() {
|
||||
None
|
||||
} else {
|
||||
render_text(layout_context, node, pseudo, style, string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How a counter value is to be rendered.
|
||||
enum RenderingMode<'a> {
|
||||
/// The innermost counter value is rendered with no extra decoration.
|
||||
Plain,
|
||||
/// The innermost counter value is rendered with the given string suffix.
|
||||
Suffix(&'a str),
|
||||
/// All values of the counter are rendered with the given separator string between them.
|
||||
All(&'a str),
|
||||
}
|
||||
|
||||
/// The value of a counter at a given level.
|
||||
struct CounterValue {
|
||||
/// The level of the flow tree that this corresponds to.
|
||||
level: u32,
|
||||
/// The value of the counter at this level.
|
||||
value: i32,
|
||||
}
|
||||
|
||||
/// Creates fragment info for a literal string.
|
||||
fn render_text(
|
||||
layout_context: &LayoutContext,
|
||||
node: OpaqueNode,
|
||||
pseudo: PseudoElementType,
|
||||
style: crate::ServoArc<ComputedValues>,
|
||||
string: String,
|
||||
) -> Option<SpecificFragmentInfo> {
|
||||
let mut fragments = LinkedList::new();
|
||||
let info = SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new(
|
||||
string.into_boxed_str(),
|
||||
None,
|
||||
)));
|
||||
fragments.push_back(Fragment::from_opaque_node_and_style(
|
||||
node,
|
||||
pseudo,
|
||||
style.clone(),
|
||||
style,
|
||||
RestyleDamage::rebuild_and_reflow(),
|
||||
info,
|
||||
));
|
||||
// FIXME(pcwalton): This should properly handle multiple marker fragments. This could happen
|
||||
// due to text run splitting.
|
||||
let fragments = with_thread_local_font_context(layout_context, |font_context| {
|
||||
TextRunScanner::new().scan_for_runs(font_context, fragments)
|
||||
});
|
||||
if fragments.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(fragments.fragments.into_iter().next().unwrap().specific)
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends string that represents the value rendered using the system appropriate for the given
|
||||
/// `list-style-type` onto the given string.
|
||||
fn push_representation(value: i32, list_style_type: ListStyleType, accumulator: &mut String) {
|
||||
match list_style_type {
|
||||
ListStyleType::None => {},
|
||||
ListStyleType::Disc |
|
||||
ListStyleType::Circle |
|
||||
ListStyleType::Square |
|
||||
ListStyleType::DisclosureOpen |
|
||||
ListStyleType::DisclosureClosed => accumulator.push(static_representation(list_style_type)),
|
||||
ListStyleType::Decimal => push_numeric_representation(value, &DECIMAL, accumulator),
|
||||
ListStyleType::ArabicIndic => {
|
||||
push_numeric_representation(value, &ARABIC_INDIC, accumulator)
|
||||
},
|
||||
ListStyleType::Bengali => push_numeric_representation(value, &BENGALI, accumulator),
|
||||
ListStyleType::Cambodian | ListStyleType::Khmer => {
|
||||
push_numeric_representation(value, &CAMBODIAN, accumulator)
|
||||
},
|
||||
ListStyleType::CjkDecimal => push_numeric_representation(value, &CJK_DECIMAL, accumulator),
|
||||
ListStyleType::Devanagari => push_numeric_representation(value, &DEVANAGARI, accumulator),
|
||||
ListStyleType::Gujarati => push_numeric_representation(value, &GUJARATI, accumulator),
|
||||
ListStyleType::Gurmukhi => push_numeric_representation(value, &GURMUKHI, accumulator),
|
||||
ListStyleType::Kannada => push_numeric_representation(value, &KANNADA, accumulator),
|
||||
ListStyleType::Lao => push_numeric_representation(value, &LAO, accumulator),
|
||||
ListStyleType::Malayalam => push_numeric_representation(value, &MALAYALAM, accumulator),
|
||||
ListStyleType::Mongolian => push_numeric_representation(value, &MONGOLIAN, accumulator),
|
||||
ListStyleType::Myanmar => push_numeric_representation(value, &MYANMAR, accumulator),
|
||||
ListStyleType::Oriya => push_numeric_representation(value, &ORIYA, accumulator),
|
||||
ListStyleType::Persian => push_numeric_representation(value, &PERSIAN, accumulator),
|
||||
ListStyleType::Telugu => push_numeric_representation(value, &TELUGU, accumulator),
|
||||
ListStyleType::Thai => push_numeric_representation(value, &THAI, accumulator),
|
||||
ListStyleType::Tibetan => push_numeric_representation(value, &TIBETAN, accumulator),
|
||||
ListStyleType::LowerAlpha => {
|
||||
push_alphabetic_representation(value, &LOWER_ALPHA, accumulator)
|
||||
},
|
||||
ListStyleType::UpperAlpha => {
|
||||
push_alphabetic_representation(value, &UPPER_ALPHA, accumulator)
|
||||
},
|
||||
ListStyleType::CjkEarthlyBranch => {
|
||||
push_alphabetic_representation(value, &CJK_EARTHLY_BRANCH, accumulator)
|
||||
},
|
||||
ListStyleType::CjkHeavenlyStem => {
|
||||
push_alphabetic_representation(value, &CJK_HEAVENLY_STEM, accumulator)
|
||||
},
|
||||
ListStyleType::LowerGreek => {
|
||||
push_alphabetic_representation(value, &LOWER_GREEK, accumulator)
|
||||
},
|
||||
ListStyleType::Hiragana => push_alphabetic_representation(value, &HIRAGANA, accumulator),
|
||||
ListStyleType::HiraganaIroha => {
|
||||
push_alphabetic_representation(value, &HIRAGANA_IROHA, accumulator)
|
||||
},
|
||||
ListStyleType::Katakana => push_alphabetic_representation(value, &KATAKANA, accumulator),
|
||||
ListStyleType::KatakanaIroha => {
|
||||
push_alphabetic_representation(value, &KATAKANA_IROHA, accumulator)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the static character that represents the value rendered using the given list-style, if
|
||||
/// possible.
|
||||
pub fn static_representation(list_style_type: ListStyleType) -> char {
|
||||
match list_style_type {
|
||||
ListStyleType::Disc => '•',
|
||||
ListStyleType::Circle => '◦',
|
||||
ListStyleType::Square => '▪',
|
||||
ListStyleType::DisclosureOpen => '▾',
|
||||
ListStyleType::DisclosureClosed => '‣',
|
||||
_ => panic!("No static representation for this list-style-type!"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes the string that represents the value rendered using the given *alphabetic system* onto
|
||||
/// the accumulator per CSS-COUNTER-STYLES § 3.1.4.
|
||||
fn push_alphabetic_representation(value: i32, system: &[char], accumulator: &mut String) {
|
||||
let mut abs_value = handle_negative_value(value, accumulator);
|
||||
|
||||
let mut string: SmallVec<[char; 8]> = SmallVec::new();
|
||||
while abs_value != 0 {
|
||||
// Step 1.
|
||||
abs_value = abs_value - 1;
|
||||
// Step 2.
|
||||
string.push(system[abs_value % system.len()]);
|
||||
// Step 3.
|
||||
abs_value = abs_value / system.len();
|
||||
}
|
||||
|
||||
accumulator.extend(string.iter().cloned().rev())
|
||||
}
|
||||
|
||||
/// Pushes the string that represents the value rendered using the given *numeric system* onto the
|
||||
/// accumulator per CSS-COUNTER-STYLES § 3.1.5.
|
||||
fn push_numeric_representation(value: i32, system: &[char], accumulator: &mut String) {
|
||||
let mut abs_value = handle_negative_value(value, accumulator);
|
||||
|
||||
// Step 1.
|
||||
if abs_value == 0 {
|
||||
accumulator.push(system[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2.
|
||||
let mut string: SmallVec<[char; 8]> = SmallVec::new();
|
||||
while abs_value != 0 {
|
||||
// Step 2.1.
|
||||
string.push(system[abs_value % system.len()]);
|
||||
// Step 2.2.
|
||||
abs_value = abs_value / system.len();
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
accumulator.extend(string.iter().cloned().rev())
|
||||
}
|
||||
|
||||
/// If the system uses a negative sign, handle negative values per CSS-COUNTER-STYLES § 2.
|
||||
///
|
||||
/// Returns the absolute value of the counter.
|
||||
fn handle_negative_value(value: i32, accumulator: &mut String) -> usize {
|
||||
// 3. If the counter value is negative and the counter style uses a negative sign, instead
|
||||
// generate an initial representation using the absolute value of the counter value.
|
||||
if value < 0 {
|
||||
// TODO: Support different negative signs using the 'negative' descriptor.
|
||||
// https://drafts.csswg.org/date/2015-07-16/css-counter-styles/#counter-style-negative
|
||||
accumulator.push('-');
|
||||
value.abs() as usize
|
||||
} else {
|
||||
value as usize
|
||||
}
|
||||
}
|
101
components/layout_2020/incremental.rs
Normal file
101
components/layout_2020/incremental.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
/* 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::{Flow, FlowFlags, GetBaseFlow};
|
||||
use style::computed_values::float::T as Float;
|
||||
use style::selector_parser::RestyleDamage;
|
||||
use style::servo::restyle_damage::ServoRestyleDamage;
|
||||
|
||||
/// Used in a flow traversal to indicate whether this re-layout should be incremental or not.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum RelayoutMode {
|
||||
Incremental,
|
||||
Force,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct SpecialRestyleDamage: u8 {
|
||||
#[doc = "If this flag is set, we need to reflow the entire document. This is more or less a \
|
||||
temporary hack to deal with cases that we don't handle incrementally yet."]
|
||||
const REFLOW_ENTIRE_DOCUMENT = 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn Flow {
|
||||
pub fn compute_layout_damage(&mut self) -> SpecialRestyleDamage {
|
||||
let mut special_damage = SpecialRestyleDamage::empty();
|
||||
let is_absolutely_positioned = self
|
||||
.base()
|
||||
.flags
|
||||
.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED);
|
||||
|
||||
// In addition to damage, we use this phase to compute whether nodes affect CSS counters.
|
||||
let mut has_counter_affecting_children = false;
|
||||
|
||||
{
|
||||
let self_base = self.mut_base();
|
||||
// Take a snapshot of the parent damage before updating it with damage from children.
|
||||
let parent_damage = self_base.restyle_damage;
|
||||
|
||||
for kid in self_base.children.iter_mut() {
|
||||
let child_is_absolutely_positioned = kid
|
||||
.base()
|
||||
.flags
|
||||
.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED);
|
||||
kid.mut_base().restyle_damage.insert(
|
||||
parent_damage
|
||||
.damage_for_child(is_absolutely_positioned, child_is_absolutely_positioned),
|
||||
);
|
||||
{
|
||||
let kid: &mut dyn Flow = kid;
|
||||
special_damage.insert(kid.compute_layout_damage());
|
||||
}
|
||||
self_base.restyle_damage.insert(
|
||||
kid.base()
|
||||
.restyle_damage
|
||||
.damage_for_parent(child_is_absolutely_positioned),
|
||||
);
|
||||
|
||||
has_counter_affecting_children = has_counter_affecting_children ||
|
||||
kid.base().flags.intersects(
|
||||
FlowFlags::AFFECTS_COUNTERS | FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let self_base = self.mut_base();
|
||||
if self_base.flags.float_kind() != Float::None &&
|
||||
self_base
|
||||
.restyle_damage
|
||||
.intersects(ServoRestyleDamage::REFLOW)
|
||||
{
|
||||
special_damage.insert(SpecialRestyleDamage::REFLOW_ENTIRE_DOCUMENT);
|
||||
}
|
||||
|
||||
if has_counter_affecting_children {
|
||||
self_base
|
||||
.flags
|
||||
.insert(FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN)
|
||||
} else {
|
||||
self_base
|
||||
.flags
|
||||
.remove(FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN)
|
||||
}
|
||||
|
||||
special_damage
|
||||
}
|
||||
|
||||
pub fn reflow_entire_document(&mut self) {
|
||||
let self_base = self.mut_base();
|
||||
self_base
|
||||
.restyle_damage
|
||||
.insert(RestyleDamage::rebuild_and_reflow());
|
||||
self_base
|
||||
.restyle_damage
|
||||
.remove(ServoRestyleDamage::RECONSTRUCT_FLOW);
|
||||
for kid in self_base.children.iter_mut() {
|
||||
kid.reflow_entire_document();
|
||||
}
|
||||
}
|
||||
}
|
2220
components/layout_2020/inline.rs
Normal file
2220
components/layout_2020/inline.rs
Normal file
File diff suppressed because it is too large
Load diff
124
components/layout_2020/layout_debug.rs
Normal file
124
components/layout_2020/layout_debug.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Supports writing a trace file created during each layout scope
|
||||
//! that can be viewed by an external tool to make layout debugging easier.
|
||||
|
||||
use crate::flow::GetBaseFlow;
|
||||
use crate::flow_ref::FlowRef;
|
||||
use serde_json::{to_string, to_value, Value};
|
||||
use std::borrow::ToOwned;
|
||||
use std::cell::RefCell;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
#[cfg(debug_assertions)]
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
thread_local!(static STATE_KEY: RefCell<Option<State>> = RefCell::new(None));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
static DEBUG_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub struct Scope;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! layout_debug_scope(
|
||||
($($arg:tt)*) => (
|
||||
if cfg!(debug_assertions) {
|
||||
layout_debug::Scope::new(format!($($arg)*))
|
||||
} else {
|
||||
layout_debug::Scope
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ScopeData {
|
||||
name: String,
|
||||
pre: Value,
|
||||
post: Value,
|
||||
children: Vec<Box<ScopeData>>,
|
||||
}
|
||||
|
||||
impl ScopeData {
|
||||
fn new(name: String, pre: Value) -> ScopeData {
|
||||
ScopeData {
|
||||
name: name,
|
||||
pre: pre,
|
||||
post: Value::Null,
|
||||
children: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
flow_root: FlowRef,
|
||||
scope_stack: Vec<Box<ScopeData>>,
|
||||
}
|
||||
|
||||
/// A layout debugging scope. The entire state of the flow tree
|
||||
/// will be output at the beginning and end of this scope.
|
||||
impl Scope {
|
||||
pub fn new(name: String) -> Scope {
|
||||
STATE_KEY.with(|ref r| {
|
||||
if let Some(ref mut state) = *r.borrow_mut() {
|
||||
let flow_trace = to_value(&state.flow_root.base()).unwrap();
|
||||
let data = Box::new(ScopeData::new(name.clone(), flow_trace));
|
||||
state.scope_stack.push(data);
|
||||
}
|
||||
});
|
||||
Scope
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl Drop for Scope {
|
||||
fn drop(&mut self) {
|
||||
STATE_KEY.with(|ref r| {
|
||||
if let Some(ref mut state) = *r.borrow_mut() {
|
||||
let mut current_scope = state.scope_stack.pop().unwrap();
|
||||
current_scope.post = to_value(&state.flow_root.base()).unwrap();
|
||||
let previous_scope = state.scope_stack.last_mut().unwrap();
|
||||
previous_scope.children.push(current_scope);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a unique ID. This is used for items such as Fragment
|
||||
/// which are often reallocated but represent essentially the
|
||||
/// same data.
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn generate_unique_debug_id() -> u16 {
|
||||
DEBUG_ID_COUNTER.fetch_add(1, Ordering::SeqCst) as u16
|
||||
}
|
||||
|
||||
/// Begin a layout debug trace. If this has not been called,
|
||||
/// creating debug scopes has no effect.
|
||||
pub fn begin_trace(flow_root: FlowRef) {
|
||||
assert!(STATE_KEY.with(|ref r| r.borrow().is_none()));
|
||||
|
||||
STATE_KEY.with(|ref r| {
|
||||
let flow_trace = to_value(&flow_root.base()).unwrap();
|
||||
let state = State {
|
||||
scope_stack: vec![Box::new(ScopeData::new("root".to_owned(), flow_trace))],
|
||||
flow_root: flow_root.clone(),
|
||||
};
|
||||
*r.borrow_mut() = Some(state);
|
||||
});
|
||||
}
|
||||
|
||||
/// End the debug layout trace. This will write the layout
|
||||
/// trace to disk in the current directory. The output
|
||||
/// file can then be viewed with an external tool.
|
||||
pub fn end_trace(generation: u32) {
|
||||
let mut thread_state = STATE_KEY.with(|ref r| r.borrow_mut().take().unwrap());
|
||||
assert_eq!(thread_state.scope_stack.len(), 1);
|
||||
let mut root_scope = thread_state.scope_stack.pop().unwrap();
|
||||
root_scope.post = to_value(&thread_state.flow_root.base()).unwrap();
|
||||
|
||||
let result = to_string(&root_scope).unwrap();
|
||||
let mut file = File::create(format!("layout_trace-{}.json", generation)).unwrap();
|
||||
file.write_all(result.as_bytes()).unwrap();
|
||||
}
|
|
@ -1,3 +1,63 @@
|
|||
/* 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/. */
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
extern crate html5ever;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate range;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
|
||||
#[macro_use]
|
||||
pub mod layout_debug;
|
||||
|
||||
pub mod animation;
|
||||
mod block;
|
||||
pub mod construct;
|
||||
pub mod context;
|
||||
pub mod data;
|
||||
pub mod display_list;
|
||||
mod flex;
|
||||
mod floats;
|
||||
pub mod flow;
|
||||
mod flow_list;
|
||||
pub mod flow_ref;
|
||||
mod fragment;
|
||||
mod generated_content;
|
||||
pub mod incremental;
|
||||
mod inline;
|
||||
mod linked_list;
|
||||
mod list_item;
|
||||
mod model;
|
||||
mod multicol;
|
||||
pub mod opaque_node;
|
||||
pub mod parallel;
|
||||
mod persistent_list;
|
||||
pub mod query;
|
||||
pub mod sequential;
|
||||
mod table;
|
||||
mod table_caption;
|
||||
mod table_cell;
|
||||
mod table_colgroup;
|
||||
mod table_row;
|
||||
mod table_rowgroup;
|
||||
mod table_wrapper;
|
||||
mod text;
|
||||
pub mod traversal;
|
||||
pub mod wrapper;
|
||||
|
||||
// For unit tests:
|
||||
pub use self::data::LayoutData;
|
||||
pub use crate::fragment::Fragment;
|
||||
pub use crate::fragment::SpecificFragmentInfo;
|
||||
|
||||
// We can't use servo_arc for everything in layout, because the Flow stuff uses
|
||||
// weak references.
|
||||
use servo_arc::Arc as ServoArc;
|
||||
|
|
21
components/layout_2020/linked_list.rs
Normal file
21
components/layout_2020/linked_list.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Utility functions for doubly-linked lists.
|
||||
|
||||
use std::collections::LinkedList;
|
||||
use std::mem;
|
||||
|
||||
/// Splits the head off a list in O(1) time, and returns the head.
|
||||
pub fn split_off_head<T>(list: &mut LinkedList<T>) -> LinkedList<T> {
|
||||
let tail = list.split_off(1);
|
||||
mem::replace(list, tail)
|
||||
}
|
||||
|
||||
/// Prepends the items in the other list to this one, leaving the other list empty.
|
||||
#[inline]
|
||||
pub fn prepend_from<T>(this: &mut LinkedList<T>, other: &mut LinkedList<T>) {
|
||||
other.append(this);
|
||||
mem::swap(this, other);
|
||||
}
|
323
components/layout_2020/list_item.rs
Normal file
323
components/layout_2020/list_item.rs
Normal file
|
@ -0,0 +1,323 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Layout for elements with a CSS `display` property of `list-item`. These elements consist of a
|
||||
//! block and an extra inline fragment for the marker.
|
||||
|
||||
use crate::block::BlockFlow;
|
||||
use crate::context::{with_thread_local_font_context, LayoutContext};
|
||||
use crate::display_list::items::DisplayListSection;
|
||||
use crate::display_list::{
|
||||
BorderPaintingMode, DisplayListBuildState, StackingContextCollectionState,
|
||||
};
|
||||
use crate::floats::FloatKind;
|
||||
use crate::flow::{Flow, FlowClass, OpaqueFlow};
|
||||
use crate::fragment::Overflow;
|
||||
use crate::fragment::{
|
||||
CoordinateSystem, Fragment, FragmentBorderBoxIterator, GeneratedContentInfo,
|
||||
};
|
||||
use crate::generated_content;
|
||||
use crate::inline::InlineFlow;
|
||||
use app_units::Au;
|
||||
use euclid::Point2D;
|
||||
use style::computed_values::list_style_type::T as ListStyleType;
|
||||
use style::computed_values::position::T as Position;
|
||||
use style::logical_geometry::LogicalSize;
|
||||
use style::properties::ComputedValues;
|
||||
use style::servo::restyle_damage::ServoRestyleDamage;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl crate::flow::HasBaseFlow for ListItemFlow {}
|
||||
|
||||
/// A block with the CSS `display` property equal to `list-item`.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct ListItemFlow {
|
||||
/// Data common to all block flows.
|
||||
pub block_flow: BlockFlow,
|
||||
/// The marker, if outside. (Markers that are inside are instead just fragments on the interior
|
||||
/// `InlineFlow`.)
|
||||
pub marker_fragments: Vec<Fragment>,
|
||||
}
|
||||
|
||||
impl ListItemFlow {
|
||||
pub fn from_fragments_and_flotation(
|
||||
main_fragment: Fragment,
|
||||
marker_fragments: Vec<Fragment>,
|
||||
flotation: Option<FloatKind>,
|
||||
) -> ListItemFlow {
|
||||
let mut this = ListItemFlow {
|
||||
block_flow: BlockFlow::from_fragment_and_float_kind(main_fragment, flotation),
|
||||
marker_fragments: marker_fragments,
|
||||
};
|
||||
|
||||
if let Some(ref marker) = this.marker_fragments.first() {
|
||||
match marker.style().get_list().list_style_type {
|
||||
ListStyleType::Disc |
|
||||
ListStyleType::None |
|
||||
ListStyleType::Circle |
|
||||
ListStyleType::Square |
|
||||
ListStyleType::DisclosureOpen |
|
||||
ListStyleType::DisclosureClosed => {},
|
||||
_ => this
|
||||
.block_flow
|
||||
.base
|
||||
.restyle_damage
|
||||
.insert(ServoRestyleDamage::RESOLVE_GENERATED_CONTENT),
|
||||
}
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Assign inline size and position for the marker. This is done during the `assign_block_size`
|
||||
/// traversal because floats will impact the marker position. Therefore we need to have already
|
||||
/// called `assign_block_size` on the list item's block flow, in order to know which floats
|
||||
/// impact the position.
|
||||
///
|
||||
/// Per CSS 2.1 § 12.5.1, the marker position is not precisely specified, but it must be on the
|
||||
/// left side of the content (for ltr direction). However, flowing the marker around floats
|
||||
/// matches the rendering of Gecko and Blink.
|
||||
fn assign_marker_inline_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
let base = &self.block_flow.base;
|
||||
let available_rect = base.floats.available_rect(
|
||||
-base.position.size.block,
|
||||
base.position.size.block,
|
||||
base.block_container_inline_size,
|
||||
);
|
||||
let mut marker_inline_start = available_rect
|
||||
.unwrap_or(self.block_flow.fragment.border_box)
|
||||
.start
|
||||
.i;
|
||||
|
||||
for marker in self.marker_fragments.iter_mut().rev() {
|
||||
let container_block_size = self
|
||||
.block_flow
|
||||
.explicit_block_containing_size(layout_context.shared_context());
|
||||
marker.assign_replaced_inline_size_if_necessary(
|
||||
base.block_container_inline_size,
|
||||
container_block_size,
|
||||
);
|
||||
|
||||
// Do this now. There's no need to do this in bubble-widths, since markers do not
|
||||
// contribute to the inline size of this flow.
|
||||
let intrinsic_inline_sizes = marker.compute_intrinsic_inline_sizes();
|
||||
|
||||
marker.border_box.size.inline = intrinsic_inline_sizes
|
||||
.content_intrinsic_sizes
|
||||
.preferred_inline_size;
|
||||
marker_inline_start = marker_inline_start - marker.border_box.size.inline;
|
||||
marker.border_box.start.i = marker_inline_start;
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_marker_block_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
// FIXME(pcwalton): Do this during flow construction, like `InlineFlow` does?
|
||||
let marker_line_metrics = with_thread_local_font_context(layout_context, |font_context| {
|
||||
InlineFlow::minimum_line_metrics_for_fragments(
|
||||
&self.marker_fragments,
|
||||
font_context,
|
||||
&*self.block_flow.fragment.style,
|
||||
)
|
||||
});
|
||||
|
||||
for marker in &mut self.marker_fragments {
|
||||
marker.assign_replaced_block_size_if_necessary();
|
||||
let marker_inline_metrics = marker.aligned_inline_metrics(
|
||||
layout_context,
|
||||
&marker_line_metrics,
|
||||
Some(&marker_line_metrics),
|
||||
);
|
||||
marker.border_box.start.b =
|
||||
marker_line_metrics.space_above_baseline - marker_inline_metrics.ascent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for ListItemFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
FlowClass::ListItem
|
||||
}
|
||||
|
||||
fn as_mut_block(&mut self) -> &mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
fn as_block(&self) -> &BlockFlow {
|
||||
&self.block_flow
|
||||
}
|
||||
|
||||
fn bubble_inline_sizes(&mut self) {
|
||||
// The marker contributes no intrinsic inline-size, so…
|
||||
self.block_flow.bubble_inline_sizes()
|
||||
}
|
||||
|
||||
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow.assign_inline_sizes(layout_context);
|
||||
}
|
||||
|
||||
fn assign_block_size(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow.assign_block_size(layout_context);
|
||||
self.assign_marker_inline_sizes(layout_context);
|
||||
self.assign_marker_block_sizes(layout_context);
|
||||
}
|
||||
|
||||
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow
|
||||
.compute_stacking_relative_position(layout_context)
|
||||
}
|
||||
|
||||
fn place_float_if_applicable<'a>(&mut self) {
|
||||
self.block_flow.place_float_if_applicable()
|
||||
}
|
||||
|
||||
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
|
||||
self.block_flow.contains_roots_of_absolute_flow_tree()
|
||||
}
|
||||
|
||||
fn is_absolute_containing_block(&self) -> bool {
|
||||
self.block_flow.is_absolute_containing_block()
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_inline_position_if_necessary(inline_position)
|
||||
}
|
||||
|
||||
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_block_position_if_necessary(block_position)
|
||||
}
|
||||
|
||||
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
|
||||
// Draw the marker, if applicable.
|
||||
for marker in &mut self.marker_fragments {
|
||||
let stacking_relative_border_box = self
|
||||
.block_flow
|
||||
.base
|
||||
.stacking_relative_border_box_for_display_list(marker);
|
||||
marker.build_display_list(
|
||||
state,
|
||||
stacking_relative_border_box,
|
||||
BorderPaintingMode::Separate,
|
||||
DisplayListSection::Content,
|
||||
self.block_flow.base.clip,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
// Draw the rest of the block.
|
||||
self.block_flow
|
||||
.build_display_list_for_block(state, BorderPaintingMode::Separate)
|
||||
}
|
||||
|
||||
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
|
||||
self.block_flow.collect_stacking_contexts(state);
|
||||
}
|
||||
|
||||
fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
|
||||
self.block_flow.repair_style(new_style)
|
||||
}
|
||||
|
||||
fn compute_overflow(&self) -> Overflow {
|
||||
let mut overflow = self.block_flow.compute_overflow();
|
||||
let flow_size = self
|
||||
.block_flow
|
||||
.base
|
||||
.position
|
||||
.size
|
||||
.to_physical(self.block_flow.base.writing_mode);
|
||||
let relative_containing_block_size = &self
|
||||
.block_flow
|
||||
.base
|
||||
.early_absolute_position_info
|
||||
.relative_containing_block_size;
|
||||
|
||||
for fragment in &self.marker_fragments {
|
||||
overflow.union(&fragment.compute_overflow(&flow_size, &relative_containing_block_size))
|
||||
}
|
||||
overflow
|
||||
}
|
||||
|
||||
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||
self.block_flow.generated_containing_block_size(flow)
|
||||
}
|
||||
|
||||
/// The 'position' property of this flow.
|
||||
fn positioning(&self) -> Position {
|
||||
self.block_flow.positioning()
|
||||
}
|
||||
|
||||
fn iterate_through_fragment_border_boxes(
|
||||
&self,
|
||||
iterator: &mut dyn FragmentBorderBoxIterator,
|
||||
level: i32,
|
||||
stacking_context_position: &Point2D<Au>,
|
||||
) {
|
||||
self.block_flow.iterate_through_fragment_border_boxes(
|
||||
iterator,
|
||||
level,
|
||||
stacking_context_position,
|
||||
);
|
||||
|
||||
for marker in &self.marker_fragments {
|
||||
if iterator.should_process(marker) {
|
||||
iterator.process(
|
||||
marker,
|
||||
level,
|
||||
&marker
|
||||
.stacking_relative_border_box(
|
||||
&self.block_flow.base.stacking_relative_position,
|
||||
&self
|
||||
.block_flow
|
||||
.base
|
||||
.early_absolute_position_info
|
||||
.relative_containing_block_size,
|
||||
self.block_flow
|
||||
.base
|
||||
.early_absolute_position_info
|
||||
.relative_containing_block_mode,
|
||||
CoordinateSystem::Own,
|
||||
)
|
||||
.translate(&stacking_context_position.to_vector()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
|
||||
self.block_flow.mutate_fragments(mutator);
|
||||
|
||||
for marker in &mut self.marker_fragments {
|
||||
(*mutator)(marker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of content that `list-style-type` results in.
|
||||
pub enum ListStyleTypeContent {
|
||||
None,
|
||||
StaticText(char),
|
||||
GeneratedContent(Box<GeneratedContentInfo>),
|
||||
}
|
||||
|
||||
impl ListStyleTypeContent {
|
||||
/// Returns the content to be used for the given value of the `list-style-type` property.
|
||||
pub fn from_list_style_type(list_style_type: ListStyleType) -> ListStyleTypeContent {
|
||||
// Just to keep things simple, use a nonbreaking space (Unicode 0xa0) to provide the marker
|
||||
// separation.
|
||||
match list_style_type {
|
||||
ListStyleType::None => ListStyleTypeContent::None,
|
||||
ListStyleType::Disc |
|
||||
ListStyleType::Circle |
|
||||
ListStyleType::Square |
|
||||
ListStyleType::DisclosureOpen |
|
||||
ListStyleType::DisclosureClosed => {
|
||||
let text = generated_content::static_representation(list_style_type);
|
||||
ListStyleTypeContent::StaticText(text)
|
||||
},
|
||||
_ => ListStyleTypeContent::GeneratedContent(Box::new(GeneratedContentInfo::ListItem)),
|
||||
}
|
||||
}
|
||||
}
|
609
components/layout_2020/model.rs
Normal file
609
components/layout_2020/model.rs
Normal file
|
@ -0,0 +1,609 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Borders, padding, and margins.
|
||||
|
||||
use crate::fragment::Fragment;
|
||||
use app_units::Au;
|
||||
use euclid::SideOffsets2D;
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt;
|
||||
use style::logical_geometry::{LogicalMargin, WritingMode};
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::MaxSize;
|
||||
use style::values::computed::{LengthPercentageOrAuto, Size};
|
||||
|
||||
/// A collapsible margin. See CSS 2.1 § 8.3.1.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct AdjoiningMargins {
|
||||
/// The value of the greatest positive margin.
|
||||
pub most_positive: Au,
|
||||
|
||||
/// The actual value (not the absolute value) of the negative margin with the largest absolute
|
||||
/// value. Since this is not the absolute value, this is always zero or negative.
|
||||
pub most_negative: Au,
|
||||
}
|
||||
|
||||
impl AdjoiningMargins {
|
||||
pub fn new() -> AdjoiningMargins {
|
||||
AdjoiningMargins {
|
||||
most_positive: Au(0),
|
||||
most_negative: Au(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_margin(margin_value: Au) -> AdjoiningMargins {
|
||||
if margin_value >= Au(0) {
|
||||
AdjoiningMargins {
|
||||
most_positive: margin_value,
|
||||
most_negative: Au(0),
|
||||
}
|
||||
} else {
|
||||
AdjoiningMargins {
|
||||
most_positive: Au(0),
|
||||
most_negative: margin_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn union(&mut self, other: AdjoiningMargins) {
|
||||
self.most_positive = max(self.most_positive, other.most_positive);
|
||||
self.most_negative = min(self.most_negative, other.most_negative)
|
||||
}
|
||||
|
||||
pub fn collapse(&self) -> Au {
|
||||
self.most_positive + self.most_negative
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the block-start and block-end margins of a flow with collapsible margins. See CSS 2.1 § 8.3.1.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum CollapsibleMargins {
|
||||
/// Margins may not collapse with this flow.
|
||||
None(Au, Au),
|
||||
|
||||
/// Both the block-start and block-end margins (specified here in that order) may collapse, but the
|
||||
/// margins do not collapse through this flow.
|
||||
Collapse(AdjoiningMargins, AdjoiningMargins),
|
||||
|
||||
/// Margins collapse *through* this flow. This means, essentially, that the flow doesn’t
|
||||
/// have any border, padding, or out-of-flow (floating or positioned) content
|
||||
CollapseThrough(AdjoiningMargins),
|
||||
}
|
||||
|
||||
impl CollapsibleMargins {
|
||||
pub fn new() -> CollapsibleMargins {
|
||||
CollapsibleMargins::None(Au(0), Au(0))
|
||||
}
|
||||
|
||||
/// Returns the amount of margin that should be applied in a noncollapsible context. This is
|
||||
/// currently used to apply block-start margin for hypothetical boxes, since we do not collapse
|
||||
/// margins of hypothetical boxes.
|
||||
pub fn block_start_margin_for_noncollapsible_context(&self) -> Au {
|
||||
match *self {
|
||||
CollapsibleMargins::None(block_start, _) => block_start,
|
||||
CollapsibleMargins::Collapse(ref block_start, _) |
|
||||
CollapsibleMargins::CollapseThrough(ref block_start) => block_start.collapse(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_end_margin_for_noncollapsible_context(&self) -> Au {
|
||||
match *self {
|
||||
CollapsibleMargins::None(_, block_end) => block_end,
|
||||
CollapsibleMargins::Collapse(_, ref block_end) |
|
||||
CollapsibleMargins::CollapseThrough(ref block_end) => block_end.collapse(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum FinalMarginState {
|
||||
MarginsCollapseThrough,
|
||||
BottomMarginCollapses,
|
||||
}
|
||||
|
||||
pub struct MarginCollapseInfo {
|
||||
pub state: MarginCollapseState,
|
||||
pub block_start_margin: AdjoiningMargins,
|
||||
pub margin_in: AdjoiningMargins,
|
||||
}
|
||||
|
||||
impl MarginCollapseInfo {
|
||||
pub fn initialize_block_start_margin(
|
||||
fragment: &Fragment,
|
||||
can_collapse_block_start_margin_with_kids: bool,
|
||||
) -> MarginCollapseInfo {
|
||||
MarginCollapseInfo {
|
||||
state: if can_collapse_block_start_margin_with_kids {
|
||||
MarginCollapseState::AccumulatingCollapsibleTopMargin
|
||||
} else {
|
||||
MarginCollapseState::AccumulatingMarginIn
|
||||
},
|
||||
block_start_margin: AdjoiningMargins::from_margin(fragment.margin.block_start),
|
||||
margin_in: AdjoiningMargins::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish_and_compute_collapsible_margins(
|
||||
mut self,
|
||||
fragment: &Fragment,
|
||||
containing_block_size: Option<Au>,
|
||||
can_collapse_block_end_margin_with_kids: bool,
|
||||
mut may_collapse_through: bool,
|
||||
) -> (CollapsibleMargins, Au) {
|
||||
let state = match self.state {
|
||||
MarginCollapseState::AccumulatingCollapsibleTopMargin => {
|
||||
may_collapse_through = may_collapse_through &&
|
||||
match fragment.style().content_block_size() {
|
||||
Size::Auto => true,
|
||||
Size::LengthPercentage(ref lp) => {
|
||||
lp.is_definitely_zero() ||
|
||||
lp.maybe_to_used_value(containing_block_size).is_none()
|
||||
},
|
||||
};
|
||||
|
||||
if may_collapse_through {
|
||||
if fragment.style.min_block_size().is_auto() ||
|
||||
fragment.style().min_block_size().is_definitely_zero()
|
||||
{
|
||||
FinalMarginState::MarginsCollapseThrough
|
||||
} else {
|
||||
// If the fragment has non-zero min-block-size, margins may not
|
||||
// collapse through it.
|
||||
FinalMarginState::BottomMarginCollapses
|
||||
}
|
||||
} else {
|
||||
// If the fragment has an explicitly specified block-size, margins may not
|
||||
// collapse through it.
|
||||
FinalMarginState::BottomMarginCollapses
|
||||
}
|
||||
},
|
||||
MarginCollapseState::AccumulatingMarginIn => FinalMarginState::BottomMarginCollapses,
|
||||
};
|
||||
|
||||
// Different logic is needed here depending on whether this flow can collapse its block-end
|
||||
// margin with its children.
|
||||
let block_end_margin = fragment.margin.block_end;
|
||||
if !can_collapse_block_end_margin_with_kids {
|
||||
match state {
|
||||
FinalMarginState::MarginsCollapseThrough => {
|
||||
let advance = self.block_start_margin.collapse();
|
||||
self.margin_in
|
||||
.union(AdjoiningMargins::from_margin(block_end_margin));
|
||||
(
|
||||
CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
|
||||
advance,
|
||||
)
|
||||
},
|
||||
FinalMarginState::BottomMarginCollapses => {
|
||||
let advance = self.margin_in.collapse();
|
||||
self.margin_in
|
||||
.union(AdjoiningMargins::from_margin(block_end_margin));
|
||||
(
|
||||
CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
|
||||
advance,
|
||||
)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
match state {
|
||||
FinalMarginState::MarginsCollapseThrough => {
|
||||
self.block_start_margin
|
||||
.union(AdjoiningMargins::from_margin(block_end_margin));
|
||||
(
|
||||
CollapsibleMargins::CollapseThrough(self.block_start_margin),
|
||||
Au(0),
|
||||
)
|
||||
},
|
||||
FinalMarginState::BottomMarginCollapses => {
|
||||
self.margin_in
|
||||
.union(AdjoiningMargins::from_margin(block_end_margin));
|
||||
(
|
||||
CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
|
||||
Au(0),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_float_ceiling(&mut self) -> Au {
|
||||
match self.state {
|
||||
MarginCollapseState::AccumulatingCollapsibleTopMargin => {
|
||||
// We do not include the top margin in the float ceiling, because the float flow
|
||||
// needs to be positioned relative to our *border box*, not our margin box. See
|
||||
// `tests/ref/float_under_top_margin_a.html`.
|
||||
Au(0)
|
||||
},
|
||||
MarginCollapseState::AccumulatingMarginIn => self.margin_in.collapse(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the child's potentially collapsible block-start margin to the current margin state and
|
||||
/// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
|
||||
/// that should be added to the Y offset during block layout.
|
||||
pub fn advance_block_start_margin(
|
||||
&mut self,
|
||||
child_collapsible_margins: &CollapsibleMargins,
|
||||
can_collapse_block_start_margin: bool,
|
||||
) -> Au {
|
||||
if !can_collapse_block_start_margin {
|
||||
self.state = MarginCollapseState::AccumulatingMarginIn
|
||||
}
|
||||
|
||||
match (self.state, *child_collapsible_margins) {
|
||||
(
|
||||
MarginCollapseState::AccumulatingCollapsibleTopMargin,
|
||||
CollapsibleMargins::None(block_start, _),
|
||||
) => {
|
||||
self.state = MarginCollapseState::AccumulatingMarginIn;
|
||||
block_start
|
||||
},
|
||||
(
|
||||
MarginCollapseState::AccumulatingCollapsibleTopMargin,
|
||||
CollapsibleMargins::Collapse(block_start, _),
|
||||
) => {
|
||||
self.block_start_margin.union(block_start);
|
||||
self.state = MarginCollapseState::AccumulatingMarginIn;
|
||||
Au(0)
|
||||
},
|
||||
(
|
||||
MarginCollapseState::AccumulatingMarginIn,
|
||||
CollapsibleMargins::None(block_start, _),
|
||||
) => {
|
||||
let previous_margin_value = self.margin_in.collapse();
|
||||
self.margin_in = AdjoiningMargins::new();
|
||||
previous_margin_value + block_start
|
||||
},
|
||||
(
|
||||
MarginCollapseState::AccumulatingMarginIn,
|
||||
CollapsibleMargins::Collapse(block_start, _),
|
||||
) => {
|
||||
self.margin_in.union(block_start);
|
||||
let margin_value = self.margin_in.collapse();
|
||||
self.margin_in = AdjoiningMargins::new();
|
||||
margin_value
|
||||
},
|
||||
(_, CollapsibleMargins::CollapseThrough(_)) => {
|
||||
// For now, we ignore this; this will be handled by `advance_block_end_margin`
|
||||
// below.
|
||||
Au(0)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the child's potentially collapsible block-end margin to the current margin state and
|
||||
/// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
|
||||
/// that should be added to the Y offset during block layout.
|
||||
pub fn advance_block_end_margin(
|
||||
&mut self,
|
||||
child_collapsible_margins: &CollapsibleMargins,
|
||||
) -> Au {
|
||||
match (self.state, *child_collapsible_margins) {
|
||||
(
|
||||
MarginCollapseState::AccumulatingCollapsibleTopMargin,
|
||||
CollapsibleMargins::None(..),
|
||||
) |
|
||||
(
|
||||
MarginCollapseState::AccumulatingCollapsibleTopMargin,
|
||||
CollapsibleMargins::Collapse(..),
|
||||
) => {
|
||||
// Can't happen because the state will have been replaced with
|
||||
// `MarginCollapseState::AccumulatingMarginIn` above.
|
||||
panic!("should not be accumulating collapsible block_start margins anymore!")
|
||||
},
|
||||
(
|
||||
MarginCollapseState::AccumulatingCollapsibleTopMargin,
|
||||
CollapsibleMargins::CollapseThrough(margin),
|
||||
) => {
|
||||
self.block_start_margin.union(margin);
|
||||
Au(0)
|
||||
},
|
||||
(MarginCollapseState::AccumulatingMarginIn, CollapsibleMargins::None(_, block_end)) => {
|
||||
assert_eq!(self.margin_in.most_positive, Au(0));
|
||||
assert_eq!(self.margin_in.most_negative, Au(0));
|
||||
block_end
|
||||
},
|
||||
(
|
||||
MarginCollapseState::AccumulatingMarginIn,
|
||||
CollapsibleMargins::Collapse(_, block_end),
|
||||
) |
|
||||
(
|
||||
MarginCollapseState::AccumulatingMarginIn,
|
||||
CollapsibleMargins::CollapseThrough(block_end),
|
||||
) => {
|
||||
self.margin_in.union(block_end);
|
||||
Au(0)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum MarginCollapseState {
|
||||
/// We are accumulating margin on the logical top of this flow.
|
||||
AccumulatingCollapsibleTopMargin,
|
||||
/// We are accumulating margin between two blocks.
|
||||
AccumulatingMarginIn,
|
||||
}
|
||||
|
||||
/// Intrinsic inline-sizes, which consist of minimum and preferred.
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
pub struct IntrinsicISizes {
|
||||
/// The *minimum inline-size* of the content.
|
||||
pub minimum_inline_size: Au,
|
||||
/// The *preferred inline-size* of the content.
|
||||
pub preferred_inline_size: Au,
|
||||
}
|
||||
|
||||
impl fmt::Debug for IntrinsicISizes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"min={:?}, pref={:?}",
|
||||
self.minimum_inline_size, self.preferred_inline_size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntrinsicISizes {
|
||||
pub fn new() -> IntrinsicISizes {
|
||||
IntrinsicISizes {
|
||||
minimum_inline_size: Au(0),
|
||||
preferred_inline_size: Au(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The temporary result of the computation of intrinsic inline-sizes.
|
||||
#[derive(Debug)]
|
||||
pub struct IntrinsicISizesContribution {
|
||||
/// Intrinsic sizes for the content only (not counting borders, padding, or margins).
|
||||
pub content_intrinsic_sizes: IntrinsicISizes,
|
||||
/// The inline size of borders and padding, as well as margins if appropriate.
|
||||
pub surrounding_size: Au,
|
||||
}
|
||||
|
||||
impl IntrinsicISizesContribution {
|
||||
/// Creates and initializes an inline size computation with all sizes set to zero.
|
||||
pub fn new() -> IntrinsicISizesContribution {
|
||||
IntrinsicISizesContribution {
|
||||
content_intrinsic_sizes: IntrinsicISizes::new(),
|
||||
surrounding_size: Au(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the content intrinsic sizes and the surrounding size together to yield the final
|
||||
/// intrinsic size computation.
|
||||
pub fn finish(self) -> IntrinsicISizes {
|
||||
IntrinsicISizes {
|
||||
minimum_inline_size: self.content_intrinsic_sizes.minimum_inline_size +
|
||||
self.surrounding_size,
|
||||
preferred_inline_size: self.content_intrinsic_sizes.preferred_inline_size +
|
||||
self.surrounding_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the computation so that the minimum is the maximum of the current minimum and the
|
||||
/// given minimum and the preferred is the sum of the current preferred and the given
|
||||
/// preferred. This is used when laying out fragments in the inline direction.
|
||||
///
|
||||
/// FIXME(pcwalton): This is incorrect when the inline fragment contains forced line breaks
|
||||
/// (e.g. `<br>` or `white-space: pre`).
|
||||
pub fn union_inline(&mut self, sizes: &IntrinsicISizes) {
|
||||
self.content_intrinsic_sizes.minimum_inline_size = max(
|
||||
self.content_intrinsic_sizes.minimum_inline_size,
|
||||
sizes.minimum_inline_size,
|
||||
);
|
||||
self.content_intrinsic_sizes.preferred_inline_size =
|
||||
self.content_intrinsic_sizes.preferred_inline_size + sizes.preferred_inline_size
|
||||
}
|
||||
|
||||
/// Updates the computation so that the minimum is the sum of the current minimum and the
|
||||
/// given minimum and the preferred is the sum of the current preferred and the given
|
||||
/// preferred. This is used when laying out fragments in the inline direction when
|
||||
/// `white-space` is `pre` or `nowrap`.
|
||||
pub fn union_nonbreaking_inline(&mut self, sizes: &IntrinsicISizes) {
|
||||
self.content_intrinsic_sizes.minimum_inline_size =
|
||||
self.content_intrinsic_sizes.minimum_inline_size + sizes.minimum_inline_size;
|
||||
self.content_intrinsic_sizes.preferred_inline_size =
|
||||
self.content_intrinsic_sizes.preferred_inline_size + sizes.preferred_inline_size
|
||||
}
|
||||
|
||||
/// Updates the computation so that the minimum is the maximum of the current minimum and the
|
||||
/// given minimum and the preferred is the maximum of the current preferred and the given
|
||||
/// preferred. This can be useful when laying out fragments in the block direction (but note
|
||||
/// that it does not take floats into account, so `BlockFlow` does not use it).
|
||||
///
|
||||
/// This is used when contributing the intrinsic sizes for individual fragments.
|
||||
pub fn union_block(&mut self, sizes: &IntrinsicISizes) {
|
||||
self.content_intrinsic_sizes.minimum_inline_size = max(
|
||||
self.content_intrinsic_sizes.minimum_inline_size,
|
||||
sizes.minimum_inline_size,
|
||||
);
|
||||
self.content_intrinsic_sizes.preferred_inline_size = max(
|
||||
self.content_intrinsic_sizes.preferred_inline_size,
|
||||
sizes.preferred_inline_size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Useful helper data type when computing values for blocks and positioned elements.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum MaybeAuto {
|
||||
Auto,
|
||||
Specified(Au),
|
||||
}
|
||||
|
||||
impl MaybeAuto {
|
||||
#[inline]
|
||||
pub fn from_style(length: LengthPercentageOrAuto, containing_length: Au) -> MaybeAuto {
|
||||
match length {
|
||||
LengthPercentageOrAuto::Auto => MaybeAuto::Auto,
|
||||
LengthPercentageOrAuto::LengthPercentage(ref lp) => {
|
||||
MaybeAuto::Specified(lp.to_used_value(containing_length))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_option(au: Option<Au>) -> MaybeAuto {
|
||||
match au {
|
||||
Some(l) => MaybeAuto::Specified(l),
|
||||
_ => MaybeAuto::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_option(&self) -> Option<Au> {
|
||||
match *self {
|
||||
MaybeAuto::Specified(value) => Some(value),
|
||||
MaybeAuto::Auto => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn specified_or_default(&self, default: Au) -> Au {
|
||||
match *self {
|
||||
MaybeAuto::Auto => default,
|
||||
MaybeAuto::Specified(value) => value,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn specified_or_zero(&self) -> Au {
|
||||
self.specified_or_default(Au::new(0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_auto(&self) -> bool {
|
||||
match *self {
|
||||
MaybeAuto::Auto => true,
|
||||
MaybeAuto::Specified(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map<F>(&self, mapper: F) -> MaybeAuto
|
||||
where
|
||||
F: FnOnce(Au) -> Au,
|
||||
{
|
||||
match *self {
|
||||
MaybeAuto::Auto => MaybeAuto::Auto,
|
||||
MaybeAuto::Specified(value) => MaybeAuto::Specified(mapper(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive an optional container size and return used value for width or height.
|
||||
///
|
||||
/// `style_length`: content size as given in the CSS.
|
||||
pub fn style_length(style_length: Size, container_size: Option<Au>) -> MaybeAuto {
|
||||
match style_length {
|
||||
Size::Auto => MaybeAuto::Auto,
|
||||
Size::LengthPercentage(ref lp) => {
|
||||
MaybeAuto::from_option(lp.0.maybe_to_used_value(container_size))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn padding_from_style(
|
||||
style: &ComputedValues,
|
||||
containing_block_inline_size: Au,
|
||||
writing_mode: WritingMode,
|
||||
) -> LogicalMargin<Au> {
|
||||
let padding_style = style.get_padding();
|
||||
LogicalMargin::from_physical(
|
||||
writing_mode,
|
||||
SideOffsets2D::new(
|
||||
padding_style
|
||||
.padding_top
|
||||
.to_used_value(containing_block_inline_size),
|
||||
padding_style
|
||||
.padding_right
|
||||
.to_used_value(containing_block_inline_size),
|
||||
padding_style
|
||||
.padding_bottom
|
||||
.to_used_value(containing_block_inline_size),
|
||||
padding_style
|
||||
.padding_left
|
||||
.to_used_value(containing_block_inline_size),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the explicitly-specified margin lengths from the given style. Percentage and auto
|
||||
/// margins are returned as zero.
|
||||
///
|
||||
/// This is used when calculating intrinsic inline sizes.
|
||||
#[inline]
|
||||
pub fn specified_margin_from_style(
|
||||
style: &ComputedValues,
|
||||
writing_mode: WritingMode,
|
||||
) -> LogicalMargin<Au> {
|
||||
let margin_style = style.get_margin();
|
||||
LogicalMargin::from_physical(
|
||||
writing_mode,
|
||||
SideOffsets2D::new(
|
||||
MaybeAuto::from_style(margin_style.margin_top, Au(0)).specified_or_zero(),
|
||||
MaybeAuto::from_style(margin_style.margin_right, Au(0)).specified_or_zero(),
|
||||
MaybeAuto::from_style(margin_style.margin_bottom, Au(0)).specified_or_zero(),
|
||||
MaybeAuto::from_style(margin_style.margin_left, Au(0)).specified_or_zero(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// A min-size and max-size constraint. The constructor has a optional `border`
|
||||
/// parameter, and when it is present the constraint will be subtracted. This is
|
||||
/// used to adjust the constraint for `box-sizing: border-box`, and when you do so
|
||||
/// make sure the size you want to clamp is intended to be used for content box.
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
pub struct SizeConstraint {
|
||||
min_size: Au,
|
||||
max_size: Option<Au>,
|
||||
}
|
||||
|
||||
impl SizeConstraint {
|
||||
/// Create a `SizeConstraint` for an axis.
|
||||
pub fn new(
|
||||
container_size: Option<Au>,
|
||||
min_size: Size,
|
||||
max_size: MaxSize,
|
||||
border: Option<Au>,
|
||||
) -> SizeConstraint {
|
||||
let mut min_size = match min_size {
|
||||
Size::Auto => Au(0),
|
||||
Size::LengthPercentage(ref lp) => {
|
||||
lp.maybe_to_used_value(container_size).unwrap_or(Au(0))
|
||||
},
|
||||
};
|
||||
|
||||
let mut max_size = match max_size {
|
||||
MaxSize::None => None,
|
||||
MaxSize::LengthPercentage(ref lp) => lp.maybe_to_used_value(container_size),
|
||||
};
|
||||
|
||||
// Make sure max size is not smaller than min size.
|
||||
max_size = max_size.map(|x| max(x, min_size));
|
||||
|
||||
if let Some(border) = border {
|
||||
min_size = max(min_size - border, Au(0));
|
||||
max_size = max_size.map(|x| max(x - border, Au(0)));
|
||||
}
|
||||
|
||||
SizeConstraint { min_size, max_size }
|
||||
}
|
||||
|
||||
/// Clamp the given size by the given min size and max size constraint.
|
||||
pub fn clamp(&self, other: Au) -> Au {
|
||||
if other < self.min_size {
|
||||
self.min_size
|
||||
} else {
|
||||
match self.max_size {
|
||||
Some(max_size) if max_size < other => max_size,
|
||||
_ => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
384
components/layout_2020/multicol.rs
Normal file
384
components/layout_2020/multicol.rs
Normal file
|
@ -0,0 +1,384 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS Multi-column layout http://dev.w3.org/csswg/css-multicol/
|
||||
|
||||
use crate::block::BlockFlow;
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::{DisplayListBuildState, StackingContextCollectionState};
|
||||
use crate::floats::FloatKind;
|
||||
use crate::flow::{Flow, FlowClass, FragmentationContext, GetBaseFlow, OpaqueFlow};
|
||||
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
|
||||
use crate::ServoArc;
|
||||
use app_units::Au;
|
||||
use euclid::{Point2D, Vector2D};
|
||||
use gfx_traits::print_tree::PrintTree;
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use style::logical_geometry::LogicalSize;
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::length::{
|
||||
MaxSize, NonNegativeLengthOrAuto, NonNegativeLengthPercentageOrNormal, Size,
|
||||
};
|
||||
use style::values::generics::column::ColumnCount;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl crate::flow::HasBaseFlow for MulticolFlow {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct MulticolFlow {
|
||||
pub block_flow: BlockFlow,
|
||||
|
||||
/// Length between the inline-start edge of a column and that of the next.
|
||||
/// That is, the used column-width + used column-gap.
|
||||
pub column_pitch: Au,
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl crate::flow::HasBaseFlow for MulticolColumnFlow {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct MulticolColumnFlow {
|
||||
pub block_flow: BlockFlow,
|
||||
}
|
||||
|
||||
impl MulticolFlow {
|
||||
pub fn from_fragment(fragment: Fragment, float_kind: Option<FloatKind>) -> MulticolFlow {
|
||||
MulticolFlow {
|
||||
block_flow: BlockFlow::from_fragment_and_float_kind(fragment, float_kind),
|
||||
column_pitch: Au(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MulticolColumnFlow {
|
||||
pub fn from_fragment(fragment: Fragment) -> MulticolColumnFlow {
|
||||
MulticolColumnFlow {
|
||||
block_flow: BlockFlow::from_fragment(fragment),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for MulticolFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
FlowClass::Multicol
|
||||
}
|
||||
|
||||
fn as_mut_block(&mut self) -> &mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
fn as_block(&self) -> &BlockFlow {
|
||||
&self.block_flow
|
||||
}
|
||||
|
||||
fn bubble_inline_sizes(&mut self) {
|
||||
// FIXME(SimonSapin) http://dev.w3.org/csswg/css-sizing/#multicol-intrinsic
|
||||
self.block_flow.bubble_inline_sizes();
|
||||
}
|
||||
|
||||
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
debug!(
|
||||
"assign_inline_sizes({}): assigning inline_size for flow",
|
||||
"multicol"
|
||||
);
|
||||
let shared_context = layout_context.shared_context();
|
||||
self.block_flow.compute_inline_sizes(shared_context);
|
||||
|
||||
// Move in from the inline-start border edge.
|
||||
let inline_start_content_edge = self.block_flow.fragment.border_box.start.i +
|
||||
self.block_flow.fragment.border_padding.inline_start;
|
||||
|
||||
// Distance from the inline-end margin edge to the inline-end content edge.
|
||||
let inline_end_content_edge = self.block_flow.fragment.margin.inline_end +
|
||||
self.block_flow.fragment.border_padding.inline_end;
|
||||
|
||||
self.block_flow.assign_inline_sizes(layout_context);
|
||||
let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
|
||||
let content_inline_size =
|
||||
self.block_flow.fragment.border_box.size.inline - padding_and_borders;
|
||||
let column_width;
|
||||
{
|
||||
let style = &self.block_flow.fragment.style;
|
||||
let column_gap = match style.get_position().column_gap {
|
||||
NonNegativeLengthPercentageOrNormal::LengthPercentage(len) => {
|
||||
len.0.to_pixel_length(content_inline_size).into()
|
||||
},
|
||||
NonNegativeLengthPercentageOrNormal::Normal => {
|
||||
self.block_flow.fragment.style.get_font().font_size.size()
|
||||
},
|
||||
};
|
||||
|
||||
let column_style = style.get_column();
|
||||
let mut column_count;
|
||||
if let NonNegativeLengthOrAuto::LengthPercentage(column_width) =
|
||||
column_style.column_width
|
||||
{
|
||||
let column_width = Au::from(column_width);
|
||||
column_count = max(
|
||||
1,
|
||||
(content_inline_size + column_gap).0 / (column_width + column_gap).0,
|
||||
);
|
||||
if let ColumnCount::Integer(specified_column_count) = column_style.column_count {
|
||||
column_count = min(column_count, specified_column_count.0 as i32);
|
||||
}
|
||||
} else {
|
||||
column_count = match column_style.column_count {
|
||||
ColumnCount::Integer(n) => n.0,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
column_width = max(
|
||||
Au(0),
|
||||
(content_inline_size + column_gap) / column_count - column_gap,
|
||||
);
|
||||
self.column_pitch = column_width + column_gap;
|
||||
}
|
||||
|
||||
self.block_flow.fragment.border_box.size.inline = content_inline_size + padding_and_borders;
|
||||
|
||||
self.block_flow.propagate_assigned_inline_size_to_children(
|
||||
shared_context,
|
||||
inline_start_content_edge,
|
||||
inline_end_content_edge,
|
||||
column_width,
|
||||
|_, _, _, _, _, _| {},
|
||||
);
|
||||
}
|
||||
|
||||
fn assign_block_size(&mut self, ctx: &LayoutContext) {
|
||||
debug!("assign_block_size: assigning block_size for multicol");
|
||||
|
||||
let fragmentation_context = Some(FragmentationContext {
|
||||
this_fragment_is_empty: true,
|
||||
available_block_size: {
|
||||
let style = &self.block_flow.fragment.style;
|
||||
let size = match style.content_block_size() {
|
||||
Size::Auto => None,
|
||||
Size::LengthPercentage(ref lp) => lp.maybe_to_used_value(None),
|
||||
};
|
||||
let size = size.or_else(|| match style.max_block_size() {
|
||||
MaxSize::None => None,
|
||||
MaxSize::LengthPercentage(ref lp) => lp.maybe_to_used_value(None),
|
||||
});
|
||||
|
||||
size.unwrap_or_else(|| {
|
||||
// FIXME: do column balancing instead
|
||||
// FIXME: (until column balancing) substract margins/borders/padding
|
||||
LogicalSize::from_physical(
|
||||
self.block_flow.base.writing_mode,
|
||||
ctx.shared_context().viewport_size(),
|
||||
)
|
||||
.block
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
// Before layout, everything is in a single "column"
|
||||
assert_eq!(self.block_flow.base.children.len(), 1);
|
||||
let mut column = self.block_flow.base.children.pop_front_arc().unwrap();
|
||||
|
||||
// Pretend there is no children for this:
|
||||
self.block_flow.assign_block_size(ctx);
|
||||
|
||||
loop {
|
||||
let remaining = Arc::get_mut(&mut column)
|
||||
.unwrap()
|
||||
.fragment(ctx, fragmentation_context);
|
||||
self.block_flow.base.children.push_back_arc(column);
|
||||
column = match remaining {
|
||||
Some(remaining) => remaining,
|
||||
None => break,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow
|
||||
.compute_stacking_relative_position(layout_context);
|
||||
let pitch = LogicalSize::new(self.block_flow.base.writing_mode, self.column_pitch, Au(0));
|
||||
let pitch = pitch.to_physical(self.block_flow.base.writing_mode);
|
||||
for (i, child) in self.block_flow.base.children.iter_mut().enumerate() {
|
||||
let point = &mut child.mut_base().stacking_relative_position;
|
||||
*point = *point + Vector2D::new(pitch.width * i as i32, pitch.height * i as i32);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_inline_position_if_necessary(inline_position)
|
||||
}
|
||||
|
||||
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_block_position_if_necessary(block_position)
|
||||
}
|
||||
|
||||
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
|
||||
debug!("build_display_list_multicol");
|
||||
self.block_flow.build_display_list(state);
|
||||
}
|
||||
|
||||
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
|
||||
self.block_flow.collect_stacking_contexts(state);
|
||||
}
|
||||
|
||||
fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
|
||||
self.block_flow.repair_style(new_style)
|
||||
}
|
||||
|
||||
fn compute_overflow(&self) -> Overflow {
|
||||
self.block_flow.compute_overflow()
|
||||
}
|
||||
|
||||
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
|
||||
self.block_flow.contains_roots_of_absolute_flow_tree()
|
||||
}
|
||||
|
||||
fn is_absolute_containing_block(&self) -> bool {
|
||||
self.block_flow.is_absolute_containing_block()
|
||||
}
|
||||
|
||||
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||
self.block_flow.generated_containing_block_size(flow)
|
||||
}
|
||||
|
||||
fn iterate_through_fragment_border_boxes(
|
||||
&self,
|
||||
iterator: &mut dyn FragmentBorderBoxIterator,
|
||||
level: i32,
|
||||
stacking_context_position: &Point2D<Au>,
|
||||
) {
|
||||
self.block_flow.iterate_through_fragment_border_boxes(
|
||||
iterator,
|
||||
level,
|
||||
stacking_context_position,
|
||||
);
|
||||
}
|
||||
|
||||
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
|
||||
self.block_flow.mutate_fragments(mutator);
|
||||
}
|
||||
|
||||
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
|
||||
self.block_flow.print_extra_flow_children(print_tree);
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for MulticolColumnFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
FlowClass::MulticolColumn
|
||||
}
|
||||
|
||||
fn as_mut_block(&mut self) -> &mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
fn as_block(&self) -> &BlockFlow {
|
||||
&self.block_flow
|
||||
}
|
||||
|
||||
fn bubble_inline_sizes(&mut self) {
|
||||
self.block_flow.bubble_inline_sizes();
|
||||
}
|
||||
|
||||
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
debug!(
|
||||
"assign_inline_sizes({}): assigning inline_size for flow",
|
||||
"multicol column"
|
||||
);
|
||||
self.block_flow.assign_inline_sizes(layout_context);
|
||||
}
|
||||
|
||||
fn assign_block_size(&mut self, ctx: &LayoutContext) {
|
||||
debug!("assign_block_size: assigning block_size for multicol column");
|
||||
self.block_flow.assign_block_size(ctx);
|
||||
}
|
||||
|
||||
fn fragment(
|
||||
&mut self,
|
||||
layout_context: &LayoutContext,
|
||||
fragmentation_context: Option<FragmentationContext>,
|
||||
) -> Option<Arc<dyn Flow>> {
|
||||
Flow::fragment(&mut self.block_flow, layout_context, fragmentation_context)
|
||||
}
|
||||
|
||||
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow
|
||||
.compute_stacking_relative_position(layout_context)
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_inline_position_if_necessary(inline_position)
|
||||
}
|
||||
|
||||
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_block_position_if_necessary(block_position)
|
||||
}
|
||||
|
||||
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
|
||||
debug!("build_display_list_multicol column");
|
||||
self.block_flow.build_display_list(state);
|
||||
}
|
||||
|
||||
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
|
||||
self.block_flow.collect_stacking_contexts(state);
|
||||
}
|
||||
|
||||
fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
|
||||
self.block_flow.repair_style(new_style)
|
||||
}
|
||||
|
||||
fn compute_overflow(&self) -> Overflow {
|
||||
self.block_flow.compute_overflow()
|
||||
}
|
||||
|
||||
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
|
||||
self.block_flow.contains_roots_of_absolute_flow_tree()
|
||||
}
|
||||
|
||||
fn is_absolute_containing_block(&self) -> bool {
|
||||
self.block_flow.is_absolute_containing_block()
|
||||
}
|
||||
|
||||
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||
self.block_flow.generated_containing_block_size(flow)
|
||||
}
|
||||
|
||||
fn iterate_through_fragment_border_boxes(
|
||||
&self,
|
||||
iterator: &mut dyn FragmentBorderBoxIterator,
|
||||
level: i32,
|
||||
stacking_context_position: &Point2D<Au>,
|
||||
) {
|
||||
self.block_flow.iterate_through_fragment_border_boxes(
|
||||
iterator,
|
||||
level,
|
||||
stacking_context_position,
|
||||
);
|
||||
}
|
||||
|
||||
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
|
||||
self.block_flow.mutate_fragments(mutator);
|
||||
}
|
||||
|
||||
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
|
||||
self.block_flow.print_extra_flow_children(print_tree);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MulticolFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "MulticolFlow: {:?}", self.block_flow)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MulticolColumnFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "MulticolColumnFlow: {:?}", self.block_flow)
|
||||
}
|
||||
}
|
19
components/layout_2020/opaque_node.rs
Normal file
19
components/layout_2020/opaque_node.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
/* 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::display_list::items::OpaqueNode;
|
||||
use libc::c_void;
|
||||
use script_traits::UntrustedNodeAddress;
|
||||
|
||||
pub trait OpaqueNodeMethods {
|
||||
/// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type
|
||||
/// of node that script expects to receive in a hit test.
|
||||
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress;
|
||||
}
|
||||
|
||||
impl OpaqueNodeMethods for OpaqueNode {
|
||||
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress {
|
||||
UntrustedNodeAddress(self.0 as *const c_void)
|
||||
}
|
||||
}
|
232
components/layout_2020/parallel.rs
Normal file
232
components/layout_2020/parallel.rs
Normal file
|
@ -0,0 +1,232 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Implements parallel traversals over the DOM and flow trees.
|
||||
//!
|
||||
//! This code is highly unsafe. Keep this file small and easy to audit.
|
||||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use crate::block::BlockFlow;
|
||||
use crate::context::LayoutContext;
|
||||
use crate::flow::{Flow, GetBaseFlow};
|
||||
use crate::flow_ref::FlowRef;
|
||||
use crate::traversal::{AssignBSizes, AssignISizes, BubbleISizes};
|
||||
use crate::traversal::{PostorderFlowTraversal, PreorderFlowTraversal};
|
||||
use profile_traits::time::{self, profile, TimerMetadata};
|
||||
use servo_config::opts;
|
||||
use smallvec::SmallVec;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicIsize, Ordering};
|
||||
|
||||
/// Traversal chunk size.
|
||||
const CHUNK_SIZE: usize = 16;
|
||||
|
||||
pub type FlowList = SmallVec<[UnsafeFlow; CHUNK_SIZE]>;
|
||||
|
||||
/// Vtable + pointer representation of a Flow trait object.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct UnsafeFlow(*const dyn Flow);
|
||||
|
||||
unsafe impl Sync for UnsafeFlow {}
|
||||
unsafe impl Send for UnsafeFlow {}
|
||||
|
||||
fn null_unsafe_flow() -> UnsafeFlow {
|
||||
UnsafeFlow(ptr::null::<BlockFlow>())
|
||||
}
|
||||
|
||||
pub fn mut_owned_flow_to_unsafe_flow(flow: *mut FlowRef) -> UnsafeFlow {
|
||||
unsafe { UnsafeFlow(&**flow) }
|
||||
}
|
||||
|
||||
/// Information that we need stored in each flow.
|
||||
pub struct FlowParallelInfo {
|
||||
/// The number of children that still need work done.
|
||||
pub children_count: AtomicIsize,
|
||||
/// The address of the parent flow.
|
||||
pub parent: UnsafeFlow,
|
||||
}
|
||||
|
||||
impl FlowParallelInfo {
|
||||
pub fn new() -> FlowParallelInfo {
|
||||
FlowParallelInfo {
|
||||
children_count: AtomicIsize::new(0),
|
||||
parent: null_unsafe_flow(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Process current flow and potentially traverse its ancestors.
|
||||
///
|
||||
/// If we are the last child that finished processing, recursively process
|
||||
/// our parent. Else, stop. Also, stop at the root.
|
||||
///
|
||||
/// Thus, if we start with all the leaves of a tree, we end up traversing
|
||||
/// the whole tree bottom-up because each parent will be processed exactly
|
||||
/// once (by the last child that finishes processing).
|
||||
///
|
||||
/// The only communication between siblings is that they both
|
||||
/// fetch-and-subtract the parent's children count.
|
||||
fn bottom_up_flow(mut unsafe_flow: UnsafeFlow, assign_bsize_traversal: &AssignBSizes) {
|
||||
loop {
|
||||
// Get a real flow.
|
||||
let flow: &mut dyn Flow = unsafe { mem::transmute(unsafe_flow) };
|
||||
|
||||
// Perform the appropriate traversal.
|
||||
if assign_bsize_traversal.should_process(flow) {
|
||||
assign_bsize_traversal.process(flow);
|
||||
}
|
||||
|
||||
let base = flow.mut_base();
|
||||
|
||||
// Reset the count of children for the next layout traversal.
|
||||
base.parallel
|
||||
.children_count
|
||||
.store(base.children.len() as isize, Ordering::Relaxed);
|
||||
|
||||
// Possibly enqueue the parent.
|
||||
let unsafe_parent = base.parallel.parent;
|
||||
if unsafe_parent == null_unsafe_flow() {
|
||||
// We're done!
|
||||
break;
|
||||
}
|
||||
|
||||
// No, we're not at the root yet. Then are we the last child
|
||||
// of our parent to finish processing? If so, we can continue
|
||||
// on with our parent; otherwise, we've gotta wait.
|
||||
let parent: &mut dyn Flow = unsafe { &mut *(unsafe_parent.0 as *mut dyn Flow) };
|
||||
let parent_base = parent.mut_base();
|
||||
if parent_base
|
||||
.parallel
|
||||
.children_count
|
||||
.fetch_sub(1, Ordering::Relaxed) ==
|
||||
1
|
||||
{
|
||||
// We were the last child of our parent. Reflow our parent.
|
||||
unsafe_flow = unsafe_parent
|
||||
} else {
|
||||
// Stop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn top_down_flow<'scope>(
|
||||
unsafe_flows: &[UnsafeFlow],
|
||||
pool: &'scope rayon::ThreadPool,
|
||||
scope: &rayon::ScopeFifo<'scope>,
|
||||
assign_isize_traversal: &'scope AssignISizes,
|
||||
assign_bsize_traversal: &'scope AssignBSizes,
|
||||
) {
|
||||
let mut discovered_child_flows = FlowList::new();
|
||||
|
||||
for unsafe_flow in unsafe_flows {
|
||||
let mut had_children = false;
|
||||
unsafe {
|
||||
// Get a real flow.
|
||||
let flow: &mut dyn Flow = mem::transmute(*unsafe_flow);
|
||||
flow.mut_base().thread_id = pool.current_thread_index().unwrap() as u8;
|
||||
|
||||
if assign_isize_traversal.should_process(flow) {
|
||||
// Perform the appropriate traversal.
|
||||
assign_isize_traversal.process(flow);
|
||||
}
|
||||
|
||||
// Possibly enqueue the children.
|
||||
for kid in flow.mut_base().child_iter_mut() {
|
||||
had_children = true;
|
||||
discovered_child_flows.push(UnsafeFlow(kid));
|
||||
}
|
||||
}
|
||||
|
||||
// If there were no more children, start assigning block-sizes.
|
||||
if !had_children {
|
||||
bottom_up_flow(*unsafe_flow, &assign_bsize_traversal)
|
||||
}
|
||||
}
|
||||
|
||||
if discovered_child_flows.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if discovered_child_flows.len() <= CHUNK_SIZE {
|
||||
// We can handle all the children in this work unit.
|
||||
top_down_flow(
|
||||
&discovered_child_flows,
|
||||
pool,
|
||||
scope,
|
||||
&assign_isize_traversal,
|
||||
&assign_bsize_traversal,
|
||||
);
|
||||
} else {
|
||||
// Spawn a new work unit for each chunk after the first.
|
||||
let mut chunks = discovered_child_flows.chunks(CHUNK_SIZE);
|
||||
let first_chunk = chunks.next();
|
||||
for chunk in chunks {
|
||||
let nodes = chunk.iter().cloned().collect::<FlowList>();
|
||||
scope.spawn_fifo(move |scope| {
|
||||
top_down_flow(
|
||||
&nodes,
|
||||
pool,
|
||||
scope,
|
||||
&assign_isize_traversal,
|
||||
&assign_bsize_traversal,
|
||||
);
|
||||
});
|
||||
}
|
||||
if let Some(chunk) = first_chunk {
|
||||
top_down_flow(
|
||||
chunk,
|
||||
pool,
|
||||
scope,
|
||||
&assign_isize_traversal,
|
||||
&assign_bsize_traversal,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the main layout passes in parallel.
|
||||
pub fn reflow(
|
||||
root: &mut dyn Flow,
|
||||
profiler_metadata: Option<TimerMetadata>,
|
||||
time_profiler_chan: time::ProfilerChan,
|
||||
context: &LayoutContext,
|
||||
queue: &rayon::ThreadPool,
|
||||
) {
|
||||
if opts::get().bubble_inline_sizes_separately {
|
||||
let bubble_inline_sizes = BubbleISizes {
|
||||
layout_context: &context,
|
||||
};
|
||||
bubble_inline_sizes.traverse(root);
|
||||
}
|
||||
|
||||
let assign_isize_traversal = &AssignISizes {
|
||||
layout_context: &context,
|
||||
};
|
||||
let assign_bsize_traversal = &AssignBSizes {
|
||||
layout_context: &context,
|
||||
};
|
||||
let nodes = [UnsafeFlow(root)];
|
||||
|
||||
queue.install(move || {
|
||||
rayon::scope_fifo(move |scope| {
|
||||
profile(
|
||||
time::ProfilerCategory::LayoutParallelWarmup,
|
||||
profiler_metadata,
|
||||
time_profiler_chan,
|
||||
move || {
|
||||
top_down_flow(
|
||||
&nodes,
|
||||
queue,
|
||||
scope,
|
||||
assign_isize_traversal,
|
||||
assign_bsize_traversal,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
101
components/layout_2020/persistent_list.rs
Normal file
101
components/layout_2020/persistent_list.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
/* 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/. */
|
||||
|
||||
//! A persistent, thread-safe singly-linked list.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct PersistentList<T> {
|
||||
head: PersistentListLink<T>,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
struct PersistentListEntry<T> {
|
||||
value: T,
|
||||
next: PersistentListLink<T>,
|
||||
}
|
||||
|
||||
type PersistentListLink<T> = Option<Arc<PersistentListEntry<T>>>;
|
||||
|
||||
impl<T> PersistentList<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
#[inline]
|
||||
pub fn new() -> PersistentList<T> {
|
||||
PersistentList {
|
||||
head: None,
|
||||
length: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.length
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn front(&self) -> Option<&T> {
|
||||
self.head.as_ref().map(|head| &head.value)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn prepend_elem(&self, value: T) -> PersistentList<T> {
|
||||
PersistentList {
|
||||
head: Some(Arc::new(PersistentListEntry {
|
||||
value: value,
|
||||
next: self.head.clone(),
|
||||
})),
|
||||
length: self.length + 1,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn iter(&self) -> PersistentListIterator<T> {
|
||||
// This could clone (and would not need the lifetime if it did), but then it would incur
|
||||
// atomic operations on every call to `.next()`. Bad.
|
||||
PersistentListIterator {
|
||||
entry: self.head.as_ref().map(|head| &**head),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for PersistentList<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
fn clone(&self) -> PersistentList<T> {
|
||||
// This establishes the persistent nature of this list: we can clone a list by just cloning
|
||||
// its head.
|
||||
PersistentList {
|
||||
head: self.head.clone(),
|
||||
length: self.length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PersistentListIterator<'a, T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
entry: Option<&'a PersistentListEntry<T>>,
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for PersistentListIterator<'a, T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
type Item = &'a T;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<&'a T> {
|
||||
let entry = self.entry?;
|
||||
let value = &entry.value;
|
||||
self.entry = match entry.next {
|
||||
None => None,
|
||||
Some(ref entry) => Some(&**entry),
|
||||
};
|
||||
Some(value)
|
||||
}
|
||||
}
|
1111
components/layout_2020/query.rs
Normal file
1111
components/layout_2020/query.rs
Normal file
File diff suppressed because it is too large
Load diff
193
components/layout_2020/sequential.rs
Normal file
193
components/layout_2020/sequential.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Implements sequential traversals over the DOM and flow trees.
|
||||
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::items::{self, CommonDisplayItem, DisplayItem, DisplayListSection};
|
||||
use crate::display_list::{DisplayListBuildState, StackingContextCollectionState};
|
||||
use crate::floats::SpeculatedFloatPlacement;
|
||||
use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
|
||||
use crate::fragment::{CoordinateSystem, FragmentBorderBoxIterator};
|
||||
use crate::generated_content::ResolveGeneratedContent;
|
||||
use crate::incremental::RelayoutMode;
|
||||
use crate::traversal::{AssignBSizes, AssignISizes, BubbleISizes, BuildDisplayList};
|
||||
use crate::traversal::{InorderFlowTraversal, PostorderFlowTraversal, PreorderFlowTraversal};
|
||||
use app_units::Au;
|
||||
use euclid::{Point2D, Rect, Size2D, Vector2D};
|
||||
use servo_config::opts;
|
||||
use style::servo::restyle_damage::ServoRestyleDamage;
|
||||
use webrender_api::units::LayoutPoint;
|
||||
|
||||
pub fn resolve_generated_content(root: &mut dyn Flow, layout_context: &LayoutContext) {
|
||||
ResolveGeneratedContent::new(&layout_context).traverse(root, 0);
|
||||
}
|
||||
|
||||
/// Run the main layout passes sequentially.
|
||||
pub fn reflow(root: &mut dyn Flow, layout_context: &LayoutContext, relayout_mode: RelayoutMode) {
|
||||
fn doit(
|
||||
flow: &mut dyn Flow,
|
||||
assign_inline_sizes: AssignISizes,
|
||||
assign_block_sizes: AssignBSizes,
|
||||
relayout_mode: RelayoutMode,
|
||||
) {
|
||||
// Force reflow children during this traversal. This is needed when we failed
|
||||
// the float speculation of a block formatting context and need to fix it.
|
||||
if relayout_mode == RelayoutMode::Force {
|
||||
flow.mut_base()
|
||||
.restyle_damage
|
||||
.insert(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
|
||||
}
|
||||
|
||||
if assign_inline_sizes.should_process(flow) {
|
||||
assign_inline_sizes.process(flow);
|
||||
}
|
||||
|
||||
for kid in flow.mut_base().child_iter_mut() {
|
||||
doit(kid, assign_inline_sizes, assign_block_sizes, relayout_mode);
|
||||
}
|
||||
|
||||
if assign_block_sizes.should_process(flow) {
|
||||
assign_block_sizes.process(flow);
|
||||
}
|
||||
}
|
||||
|
||||
if opts::get().bubble_inline_sizes_separately {
|
||||
let bubble_inline_sizes = BubbleISizes {
|
||||
layout_context: &layout_context,
|
||||
};
|
||||
bubble_inline_sizes.traverse(root);
|
||||
}
|
||||
|
||||
let assign_inline_sizes = AssignISizes {
|
||||
layout_context: &layout_context,
|
||||
};
|
||||
let assign_block_sizes = AssignBSizes {
|
||||
layout_context: &layout_context,
|
||||
};
|
||||
|
||||
doit(root, assign_inline_sizes, assign_block_sizes, relayout_mode);
|
||||
}
|
||||
|
||||
pub fn build_display_list_for_subtree<'a>(
|
||||
flow_root: &mut dyn Flow,
|
||||
layout_context: &'a LayoutContext,
|
||||
background_color: webrender_api::ColorF,
|
||||
client_size: Size2D<Au>,
|
||||
) -> DisplayListBuildState<'a> {
|
||||
let mut state = StackingContextCollectionState::new(layout_context.id);
|
||||
flow_root.collect_stacking_contexts(&mut state);
|
||||
|
||||
let mut state = DisplayListBuildState::new(layout_context, state);
|
||||
|
||||
// Create a base rectangle for the page background based on the root
|
||||
// background color.
|
||||
let base = state.create_base_display_item(
|
||||
Rect::new(Point2D::new(Au::new(0), Au::new(0)), client_size),
|
||||
flow_root.as_block().fragment.node,
|
||||
None,
|
||||
DisplayListSection::BackgroundAndBorders,
|
||||
);
|
||||
state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new(
|
||||
base,
|
||||
webrender_api::RectangleDisplayItem {
|
||||
color: background_color,
|
||||
common: items::empty_common_item_properties(),
|
||||
},
|
||||
)));
|
||||
|
||||
let mut build_display_list = BuildDisplayList { state: state };
|
||||
build_display_list.traverse(flow_root);
|
||||
build_display_list.state
|
||||
}
|
||||
|
||||
pub fn iterate_through_flow_tree_fragment_border_boxes(
|
||||
root: &mut dyn Flow,
|
||||
iterator: &mut dyn FragmentBorderBoxIterator,
|
||||
) {
|
||||
fn doit(
|
||||
flow: &mut dyn Flow,
|
||||
level: i32,
|
||||
iterator: &mut dyn FragmentBorderBoxIterator,
|
||||
stacking_context_position: &Point2D<Au>,
|
||||
) {
|
||||
flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position);
|
||||
|
||||
for kid in flow.mut_base().child_iter_mut() {
|
||||
let mut stacking_context_position = *stacking_context_position;
|
||||
if kid.is_block_flow() && kid.as_block().fragment.establishes_stacking_context() {
|
||||
stacking_context_position =
|
||||
Point2D::new(kid.as_block().fragment.margin.inline_start, Au(0)) +
|
||||
kid.base().stacking_relative_position +
|
||||
stacking_context_position.to_vector();
|
||||
let relative_position = kid
|
||||
.as_block()
|
||||
.stacking_relative_border_box(CoordinateSystem::Own);
|
||||
if let Some(matrix) = kid.as_block().fragment.transform_matrix(&relative_position) {
|
||||
let transform_matrix = matrix.transform_point2d(&LayoutPoint::zero()).unwrap();
|
||||
stacking_context_position = stacking_context_position +
|
||||
Vector2D::new(
|
||||
Au::from_f32_px(transform_matrix.x),
|
||||
Au::from_f32_px(transform_matrix.y),
|
||||
)
|
||||
}
|
||||
}
|
||||
doit(kid, level + 1, iterator, &stacking_context_position);
|
||||
}
|
||||
}
|
||||
|
||||
doit(root, 0, iterator, &Point2D::zero());
|
||||
}
|
||||
|
||||
pub fn store_overflow(layout_context: &LayoutContext, flow: &mut dyn Flow) {
|
||||
if !flow
|
||||
.base()
|
||||
.restyle_damage
|
||||
.contains(ServoRestyleDamage::STORE_OVERFLOW)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for kid in flow.mut_base().child_iter_mut() {
|
||||
store_overflow(layout_context, kid);
|
||||
}
|
||||
|
||||
flow.store_overflow(layout_context);
|
||||
|
||||
flow.mut_base()
|
||||
.restyle_damage
|
||||
.remove(ServoRestyleDamage::STORE_OVERFLOW);
|
||||
}
|
||||
|
||||
/// Guesses how much inline size will be taken up by floats on the left and right sides of the
|
||||
/// given flow. This is needed to speculatively calculate the inline sizes of block formatting
|
||||
/// contexts. The speculation typically succeeds, but if it doesn't we have to lay it out again.
|
||||
pub fn guess_float_placement(flow: &mut dyn Flow) {
|
||||
if !flow
|
||||
.base()
|
||||
.restyle_damage
|
||||
.intersects(ServoRestyleDamage::REFLOW)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut floats_in = SpeculatedFloatPlacement::compute_floats_in_for_first_child(flow);
|
||||
for kid in flow.mut_base().child_iter_mut() {
|
||||
if kid
|
||||
.base()
|
||||
.flags
|
||||
.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
|
||||
{
|
||||
// Do not propagate floats in or out, but do propogate between kids.
|
||||
guess_float_placement(kid);
|
||||
} else {
|
||||
floats_in.compute_floats_in(kid);
|
||||
kid.mut_base().speculated_float_placement_in = floats_in;
|
||||
guess_float_placement(kid);
|
||||
floats_in = kid.base().speculated_float_placement_out;
|
||||
}
|
||||
}
|
||||
floats_in.compute_floats_out(flow);
|
||||
flow.mut_base().speculated_float_placement_out = floats_in
|
||||
}
|
1378
components/layout_2020/table.rs
Normal file
1378
components/layout_2020/table.rs
Normal file
File diff suppressed because it is too large
Load diff
139
components/layout_2020/table_caption.rs
Normal file
139
components/layout_2020/table_caption.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS table formatting contexts.
|
||||
|
||||
use crate::block::BlockFlow;
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::{
|
||||
DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
|
||||
};
|
||||
use crate::flow::{Flow, FlowClass, OpaqueFlow};
|
||||
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
|
||||
use app_units::Au;
|
||||
use euclid::Point2D;
|
||||
use gfx_traits::print_tree::PrintTree;
|
||||
use std::fmt;
|
||||
use style::logical_geometry::LogicalSize;
|
||||
use style::properties::ComputedValues;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl crate::flow::HasBaseFlow for TableCaptionFlow {}
|
||||
|
||||
/// A table formatting context.
|
||||
#[repr(C)]
|
||||
pub struct TableCaptionFlow {
|
||||
pub block_flow: BlockFlow,
|
||||
}
|
||||
|
||||
impl TableCaptionFlow {
|
||||
pub fn from_fragment(fragment: Fragment) -> TableCaptionFlow {
|
||||
TableCaptionFlow {
|
||||
block_flow: BlockFlow::from_fragment(fragment),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableCaptionFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
FlowClass::TableCaption
|
||||
}
|
||||
|
||||
fn as_mut_block(&mut self) -> &mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
fn as_block(&self) -> &BlockFlow {
|
||||
&self.block_flow
|
||||
}
|
||||
|
||||
fn bubble_inline_sizes(&mut self) {
|
||||
self.block_flow.bubble_inline_sizes();
|
||||
}
|
||||
|
||||
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
debug!(
|
||||
"assign_inline_sizes({}): assigning inline_size for flow",
|
||||
"table_caption"
|
||||
);
|
||||
self.block_flow.assign_inline_sizes(layout_context);
|
||||
}
|
||||
|
||||
fn assign_block_size(&mut self, layout_context: &LayoutContext) {
|
||||
debug!("assign_block_size: assigning block_size for table_caption");
|
||||
self.block_flow.assign_block_size(layout_context);
|
||||
}
|
||||
|
||||
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow
|
||||
.compute_stacking_relative_position(layout_context)
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_inline_position_if_necessary(inline_position)
|
||||
}
|
||||
|
||||
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_block_position_if_necessary(block_position)
|
||||
}
|
||||
|
||||
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
|
||||
debug!("build_display_list_table_caption: same process as block flow");
|
||||
self.block_flow.build_display_list(state);
|
||||
}
|
||||
|
||||
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
|
||||
self.block_flow
|
||||
.collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty());
|
||||
}
|
||||
|
||||
fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
|
||||
self.block_flow.repair_style(new_style)
|
||||
}
|
||||
|
||||
fn compute_overflow(&self) -> Overflow {
|
||||
self.block_flow.compute_overflow()
|
||||
}
|
||||
|
||||
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
|
||||
self.block_flow.contains_roots_of_absolute_flow_tree()
|
||||
}
|
||||
|
||||
fn is_absolute_containing_block(&self) -> bool {
|
||||
self.block_flow.is_absolute_containing_block()
|
||||
}
|
||||
|
||||
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||
self.block_flow.generated_containing_block_size(flow)
|
||||
}
|
||||
|
||||
fn iterate_through_fragment_border_boxes(
|
||||
&self,
|
||||
iterator: &mut dyn FragmentBorderBoxIterator,
|
||||
level: i32,
|
||||
stacking_context_position: &Point2D<Au>,
|
||||
) {
|
||||
self.block_flow.iterate_through_fragment_border_boxes(
|
||||
iterator,
|
||||
level,
|
||||
stacking_context_position,
|
||||
)
|
||||
}
|
||||
|
||||
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
|
||||
self.block_flow.mutate_fragments(mutator)
|
||||
}
|
||||
|
||||
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
|
||||
self.block_flow.print_extra_flow_children(print_tree);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TableCaptionFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TableCaptionFlow: {:?}", self.block_flow)
|
||||
}
|
||||
}
|
506
components/layout_2020/table_cell.rs
Normal file
506
components/layout_2020/table_cell.rs
Normal file
|
@ -0,0 +1,506 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS table formatting contexts.
|
||||
|
||||
use crate::block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayCollapseFlag};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::{
|
||||
DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
|
||||
};
|
||||
use crate::flow::{Flow, FlowClass, FlowFlags, GetBaseFlow, OpaqueFlow};
|
||||
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
|
||||
use crate::layout_debug;
|
||||
use crate::table::InternalTable;
|
||||
use crate::table_row::{CollapsedBorder, CollapsedBorderProvenance};
|
||||
use app_units::Au;
|
||||
use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
|
||||
use gfx_traits::print_tree::PrintTree;
|
||||
use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode;
|
||||
use std::fmt;
|
||||
use style::logical_geometry::{LogicalMargin, LogicalRect, LogicalSize, WritingMode};
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::length::Size;
|
||||
use style::values::computed::Color;
|
||||
use style::values::generics::box_::{VerticalAlign, VerticalAlignKeyword};
|
||||
use style::values::specified::BorderStyle;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl crate::flow::HasBaseFlow for TableCellFlow {}
|
||||
|
||||
/// A table formatting context.
|
||||
#[derive(Serialize)]
|
||||
#[repr(C)]
|
||||
pub struct TableCellFlow {
|
||||
/// Data common to all block flows.
|
||||
pub block_flow: BlockFlow,
|
||||
|
||||
/// Border collapse information for the cell.
|
||||
pub collapsed_borders: CollapsedBordersForCell,
|
||||
|
||||
/// The column span of this cell.
|
||||
pub column_span: u32,
|
||||
|
||||
/// The rows spanned by this cell.
|
||||
pub row_span: u32,
|
||||
|
||||
/// Whether this cell is visible. If false, the value of `empty-cells` means that we must not
|
||||
/// display this cell.
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
impl TableCellFlow {
|
||||
pub fn from_fragment(fragment: Fragment) -> TableCellFlow {
|
||||
TableCellFlow {
|
||||
block_flow: BlockFlow::from_fragment(fragment),
|
||||
collapsed_borders: CollapsedBordersForCell::new(),
|
||||
column_span: 1,
|
||||
row_span: 1,
|
||||
visible: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_node_fragment_and_visibility_flag<N: ThreadSafeLayoutNode>(
|
||||
node: &N,
|
||||
fragment: Fragment,
|
||||
visible: bool,
|
||||
) -> TableCellFlow {
|
||||
TableCellFlow {
|
||||
block_flow: BlockFlow::from_fragment(fragment),
|
||||
collapsed_borders: CollapsedBordersForCell::new(),
|
||||
column_span: node.get_colspan(),
|
||||
row_span: node.get_rowspan(),
|
||||
visible: visible,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fragment(&mut self) -> &Fragment {
|
||||
&self.block_flow.fragment
|
||||
}
|
||||
|
||||
pub fn mut_fragment(&mut self) -> &mut Fragment {
|
||||
&mut self.block_flow.fragment
|
||||
}
|
||||
|
||||
/// Assign block-size for table-cell flow.
|
||||
///
|
||||
/// inline(always) because this is only ever called by in-order or non-in-order top-level
|
||||
/// methods.
|
||||
#[inline(always)]
|
||||
fn assign_block_size_table_cell_base(&mut self, layout_context: &LayoutContext) {
|
||||
let remaining = self.block_flow.assign_block_size_block_base(
|
||||
layout_context,
|
||||
None,
|
||||
MarginsMayCollapseFlag::MarginsMayNotCollapse,
|
||||
);
|
||||
debug_assert!(remaining.is_none());
|
||||
}
|
||||
|
||||
/// Position this cell's children according to vertical-align.
|
||||
pub fn valign_children(&mut self) {
|
||||
// Note to the reader: this code has been tested with negative margins.
|
||||
// We end up with a "end" that's before the "start," but the math still works out.
|
||||
let mut extents = None;
|
||||
for kid in self.base().children.iter() {
|
||||
let kid_base = kid.base();
|
||||
if kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
|
||||
continue;
|
||||
}
|
||||
let start = kid_base.position.start.b -
|
||||
kid_base
|
||||
.collapsible_margins
|
||||
.block_start_margin_for_noncollapsible_context();
|
||||
let end = kid_base.position.start.b +
|
||||
kid_base.position.size.block +
|
||||
kid_base
|
||||
.collapsible_margins
|
||||
.block_end_margin_for_noncollapsible_context();
|
||||
match extents {
|
||||
Some((ref mut first_start, ref mut last_end)) => {
|
||||
if start < *first_start {
|
||||
*first_start = start
|
||||
}
|
||||
if end > *last_end {
|
||||
*last_end = end
|
||||
}
|
||||
},
|
||||
None => extents = Some((start, end)),
|
||||
}
|
||||
}
|
||||
let (first_start, last_end) = match extents {
|
||||
Some(extents) => extents,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let kids_size = last_end - first_start;
|
||||
let self_size = self.base().position.size.block -
|
||||
self.block_flow.fragment.border_padding.block_start_end();
|
||||
let kids_self_gap = self_size - kids_size;
|
||||
|
||||
// This offset should also account for VerticalAlign::baseline.
|
||||
// Need max cell ascent from the first row of this cell.
|
||||
let offset = match self.block_flow.fragment.style().get_box().vertical_align {
|
||||
VerticalAlign::Keyword(VerticalAlignKeyword::Middle) => kids_self_gap / 2,
|
||||
VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => kids_self_gap,
|
||||
_ => Au(0),
|
||||
};
|
||||
if offset == Au(0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for kid in self.mut_base().children.iter_mut() {
|
||||
let kid_base = kid.mut_base();
|
||||
if !kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
|
||||
kid_base.position.start.b += offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Total block size of child
|
||||
//
|
||||
// Call after block size calculation
|
||||
pub fn total_block_size(&mut self) -> Au {
|
||||
// TODO: Percentage block-size
|
||||
let specified = self
|
||||
.fragment()
|
||||
.style()
|
||||
.content_block_size()
|
||||
.to_used_value(Au(0))
|
||||
.unwrap_or(Au(0));
|
||||
specified + self.fragment().border_padding.block_start_end()
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableCellFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
FlowClass::TableCell
|
||||
}
|
||||
|
||||
fn as_mut_table_cell(&mut self) -> &mut TableCellFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_table_cell(&self) -> &TableCellFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_block(&mut self) -> &mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
fn as_block(&self) -> &BlockFlow {
|
||||
&self.block_flow
|
||||
}
|
||||
|
||||
/// Minimum/preferred inline-sizes set by this function are used in automatic table layout
|
||||
/// calculation.
|
||||
fn bubble_inline_sizes(&mut self) {
|
||||
let _scope = layout_debug_scope!(
|
||||
"table_cell::bubble_inline_sizes {:x}",
|
||||
self.block_flow.base.debug_id()
|
||||
);
|
||||
|
||||
self.block_flow.bubble_inline_sizes_for_block(true);
|
||||
let specified_inline_size = match self.block_flow.fragment.style().content_inline_size() {
|
||||
Size::Auto => Au(0),
|
||||
Size::LengthPercentage(ref lp) => lp.to_used_value(Au(0)),
|
||||
};
|
||||
|
||||
if self
|
||||
.block_flow
|
||||
.base
|
||||
.intrinsic_inline_sizes
|
||||
.minimum_inline_size <
|
||||
specified_inline_size
|
||||
{
|
||||
self.block_flow
|
||||
.base
|
||||
.intrinsic_inline_sizes
|
||||
.minimum_inline_size = specified_inline_size
|
||||
}
|
||||
if self
|
||||
.block_flow
|
||||
.base
|
||||
.intrinsic_inline_sizes
|
||||
.preferred_inline_size <
|
||||
self.block_flow
|
||||
.base
|
||||
.intrinsic_inline_sizes
|
||||
.minimum_inline_size
|
||||
{
|
||||
self.block_flow
|
||||
.base
|
||||
.intrinsic_inline_sizes
|
||||
.preferred_inline_size = self
|
||||
.block_flow
|
||||
.base
|
||||
.intrinsic_inline_sizes
|
||||
.minimum_inline_size;
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
|
||||
/// When called on this context, the context has had its inline-size set by the parent table
|
||||
/// row.
|
||||
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
let _scope = layout_debug_scope!(
|
||||
"table_cell::assign_inline_sizes {:x}",
|
||||
self.block_flow.base.debug_id()
|
||||
);
|
||||
debug!(
|
||||
"assign_inline_sizes({}): assigning inline_size for flow",
|
||||
"table_cell"
|
||||
);
|
||||
|
||||
let shared_context = layout_context.shared_context();
|
||||
// The position was set to the column inline-size by the parent flow, table row flow.
|
||||
let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
|
||||
|
||||
let inline_size_computer = InternalTable;
|
||||
inline_size_computer.compute_used_inline_size(
|
||||
&mut self.block_flow,
|
||||
shared_context,
|
||||
containing_block_inline_size,
|
||||
);
|
||||
|
||||
let inline_start_content_edge = self.block_flow.fragment.border_box.start.i +
|
||||
self.block_flow.fragment.border_padding.inline_start;
|
||||
let inline_end_content_edge = self.block_flow.base.block_container_inline_size -
|
||||
self.block_flow.fragment.border_padding.inline_start_end() -
|
||||
self.block_flow.fragment.border_box.size.inline;
|
||||
let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
|
||||
let content_inline_size =
|
||||
self.block_flow.fragment.border_box.size.inline - padding_and_borders;
|
||||
|
||||
self.block_flow.propagate_assigned_inline_size_to_children(
|
||||
shared_context,
|
||||
inline_start_content_edge,
|
||||
inline_end_content_edge,
|
||||
content_inline_size,
|
||||
|_, _, _, _, _, _| {},
|
||||
);
|
||||
}
|
||||
|
||||
fn assign_block_size(&mut self, layout_context: &LayoutContext) {
|
||||
debug!("assign_block_size: assigning block_size for table_cell");
|
||||
self.assign_block_size_table_cell_base(layout_context);
|
||||
}
|
||||
|
||||
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow
|
||||
.compute_stacking_relative_position(layout_context)
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_inline_position_if_necessary(inline_position)
|
||||
}
|
||||
|
||||
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_block_position_if_necessary(block_position)
|
||||
}
|
||||
|
||||
fn build_display_list(&mut self, _: &mut DisplayListBuildState) {
|
||||
use style::servo::restyle_damage::ServoRestyleDamage;
|
||||
// This is handled by TableCellStyleInfo::build_display_list()
|
||||
// when the containing table builds its display list
|
||||
|
||||
// we skip setting the damage in TableCellStyleInfo::build_display_list()
|
||||
// because we only have immutable access
|
||||
self.block_flow
|
||||
.fragment
|
||||
.restyle_damage
|
||||
.remove(ServoRestyleDamage::REPAINT);
|
||||
}
|
||||
|
||||
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
|
||||
self.block_flow
|
||||
.collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty());
|
||||
}
|
||||
|
||||
fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
|
||||
self.block_flow.repair_style(new_style)
|
||||
}
|
||||
|
||||
fn compute_overflow(&self) -> Overflow {
|
||||
self.block_flow.compute_overflow()
|
||||
}
|
||||
|
||||
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
|
||||
self.block_flow.contains_roots_of_absolute_flow_tree()
|
||||
}
|
||||
|
||||
fn is_absolute_containing_block(&self) -> bool {
|
||||
self.block_flow.is_absolute_containing_block()
|
||||
}
|
||||
|
||||
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||
self.block_flow.generated_containing_block_size(flow)
|
||||
}
|
||||
|
||||
fn iterate_through_fragment_border_boxes(
|
||||
&self,
|
||||
iterator: &mut dyn FragmentBorderBoxIterator,
|
||||
level: i32,
|
||||
stacking_context_position: &Point2D<Au>,
|
||||
) {
|
||||
self.block_flow.iterate_through_fragment_border_boxes(
|
||||
iterator,
|
||||
level,
|
||||
stacking_context_position,
|
||||
)
|
||||
}
|
||||
|
||||
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
|
||||
self.block_flow.mutate_fragments(mutator)
|
||||
}
|
||||
|
||||
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
|
||||
self.block_flow.print_extra_flow_children(print_tree);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TableCellFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TableCellFlow: {:?}", self.block_flow)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
pub struct CollapsedBordersForCell {
|
||||
pub inline_start_border: CollapsedBorder,
|
||||
pub inline_end_border: CollapsedBorder,
|
||||
pub block_start_border: CollapsedBorder,
|
||||
pub block_end_border: CollapsedBorder,
|
||||
pub inline_start_width: Au,
|
||||
pub inline_end_width: Au,
|
||||
pub block_start_width: Au,
|
||||
pub block_end_width: Au,
|
||||
}
|
||||
|
||||
impl CollapsedBordersForCell {
|
||||
fn new() -> CollapsedBordersForCell {
|
||||
CollapsedBordersForCell {
|
||||
inline_start_border: CollapsedBorder::new(),
|
||||
inline_end_border: CollapsedBorder::new(),
|
||||
block_start_border: CollapsedBorder::new(),
|
||||
block_end_border: CollapsedBorder::new(),
|
||||
inline_start_width: Au(0),
|
||||
inline_end_width: Au(0),
|
||||
block_start_width: Au(0),
|
||||
block_end_width: Au(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn should_paint_inline_start_border(&self) -> bool {
|
||||
self.inline_start_border.provenance != CollapsedBorderProvenance::FromPreviousTableCell
|
||||
}
|
||||
|
||||
fn should_paint_inline_end_border(&self) -> bool {
|
||||
self.inline_end_border.provenance != CollapsedBorderProvenance::FromNextTableCell
|
||||
}
|
||||
|
||||
fn should_paint_block_start_border(&self) -> bool {
|
||||
self.block_start_border.provenance != CollapsedBorderProvenance::FromPreviousTableCell
|
||||
}
|
||||
|
||||
fn should_paint_block_end_border(&self) -> bool {
|
||||
self.block_end_border.provenance != CollapsedBorderProvenance::FromNextTableCell
|
||||
}
|
||||
|
||||
pub fn adjust_border_widths_for_painting(&self, border_widths: &mut LogicalMargin<Au>) {
|
||||
border_widths.inline_start = if !self.should_paint_inline_start_border() {
|
||||
Au(0)
|
||||
} else {
|
||||
self.inline_start_border.width
|
||||
};
|
||||
border_widths.inline_end = if !self.should_paint_inline_end_border() {
|
||||
Au(0)
|
||||
} else {
|
||||
self.inline_end_border.width
|
||||
};
|
||||
border_widths.block_start = if !self.should_paint_block_start_border() {
|
||||
Au(0)
|
||||
} else {
|
||||
self.block_start_border.width
|
||||
};
|
||||
border_widths.block_end = if !self.should_paint_block_end_border() {
|
||||
Au(0)
|
||||
} else {
|
||||
self.block_end_border.width
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_border_bounds_for_painting(
|
||||
&self,
|
||||
border_bounds: &mut Rect<Au>,
|
||||
writing_mode: WritingMode,
|
||||
) {
|
||||
let inline_start_divisor = if self.should_paint_inline_start_border() {
|
||||
2
|
||||
} else {
|
||||
-2
|
||||
};
|
||||
let inline_start_offset =
|
||||
self.inline_start_width / 2 + self.inline_start_border.width / inline_start_divisor;
|
||||
let inline_end_divisor = if self.should_paint_inline_end_border() {
|
||||
2
|
||||
} else {
|
||||
-2
|
||||
};
|
||||
let inline_end_offset =
|
||||
self.inline_end_width / 2 + self.inline_end_border.width / inline_end_divisor;
|
||||
let block_start_divisor = if self.should_paint_block_start_border() {
|
||||
2
|
||||
} else {
|
||||
-2
|
||||
};
|
||||
let block_start_offset =
|
||||
self.block_start_width / 2 + self.block_start_border.width / block_start_divisor;
|
||||
let block_end_divisor = if self.should_paint_block_end_border() {
|
||||
2
|
||||
} else {
|
||||
-2
|
||||
};
|
||||
let block_end_offset =
|
||||
self.block_end_width / 2 + self.block_end_border.width / block_end_divisor;
|
||||
|
||||
// FIXME(pcwalton): Get the real container size.
|
||||
let mut logical_bounds =
|
||||
LogicalRect::from_physical(writing_mode, *border_bounds, Size2D::new(Au(0), Au(0)));
|
||||
logical_bounds.start.i = logical_bounds.start.i - inline_start_offset;
|
||||
logical_bounds.start.b = logical_bounds.start.b - block_start_offset;
|
||||
logical_bounds.size.inline =
|
||||
logical_bounds.size.inline + inline_start_offset + inline_end_offset;
|
||||
logical_bounds.size.block =
|
||||
logical_bounds.size.block + block_start_offset + block_end_offset;
|
||||
*border_bounds = logical_bounds.to_physical(writing_mode, Size2D::new(Au(0), Au(0)))
|
||||
}
|
||||
|
||||
pub fn adjust_border_colors_and_styles_for_painting(
|
||||
&self,
|
||||
border_colors: &mut SideOffsets2D<Color>,
|
||||
border_styles: &mut SideOffsets2D<BorderStyle>,
|
||||
writing_mode: WritingMode,
|
||||
) {
|
||||
let logical_border_colors = LogicalMargin::new(
|
||||
writing_mode,
|
||||
self.block_start_border.color,
|
||||
self.inline_end_border.color,
|
||||
self.block_end_border.color,
|
||||
self.inline_start_border.color,
|
||||
);
|
||||
*border_colors = logical_border_colors.to_physical(writing_mode);
|
||||
|
||||
let logical_border_styles = LogicalMargin::new(
|
||||
writing_mode,
|
||||
self.block_start_border.style,
|
||||
self.inline_end_border.style,
|
||||
self.block_end_border.style,
|
||||
self.inline_start_border.style,
|
||||
);
|
||||
*border_styles = logical_border_styles.to_physical(writing_mode);
|
||||
}
|
||||
}
|
131
components/layout_2020/table_colgroup.rs
Normal file
131
components/layout_2020/table_colgroup.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS table formatting contexts.
|
||||
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::{DisplayListBuildState, StackingContextCollectionState};
|
||||
use crate::flow::{BaseFlow, Flow, FlowClass, ForceNonfloatedFlag, OpaqueFlow};
|
||||
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
|
||||
use crate::layout_debug;
|
||||
use app_units::Au;
|
||||
use euclid::Point2D;
|
||||
use std::fmt;
|
||||
use style::logical_geometry::LogicalSize;
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::Size;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl crate::flow::HasBaseFlow for TableColGroupFlow {}
|
||||
|
||||
/// A table formatting context.
|
||||
#[repr(C)]
|
||||
pub struct TableColGroupFlow {
|
||||
/// Data common to all flows.
|
||||
pub base: BaseFlow,
|
||||
|
||||
/// The associated fragment.
|
||||
pub fragment: Option<Fragment>,
|
||||
|
||||
/// The table column fragments
|
||||
pub cols: Vec<Fragment>,
|
||||
|
||||
/// The specified inline-sizes of table columns. (We use `LengthPercentageOrAuto` here in
|
||||
/// lieu of `ColumnInlineSize` because column groups do not establish minimum or preferred
|
||||
/// inline sizes.)
|
||||
pub inline_sizes: Vec<Size>,
|
||||
}
|
||||
|
||||
impl TableColGroupFlow {
|
||||
pub fn from_fragments(fragment: Fragment, fragments: Vec<Fragment>) -> TableColGroupFlow {
|
||||
let writing_mode = fragment.style().writing_mode;
|
||||
TableColGroupFlow {
|
||||
base: BaseFlow::new(
|
||||
Some(fragment.style()),
|
||||
writing_mode,
|
||||
ForceNonfloatedFlag::ForceNonfloated,
|
||||
),
|
||||
fragment: Some(fragment),
|
||||
cols: fragments,
|
||||
inline_sizes: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableColGroupFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
FlowClass::TableColGroup
|
||||
}
|
||||
|
||||
fn as_mut_table_colgroup(&mut self) -> &mut TableColGroupFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_table_colgroup(&self) -> &TableColGroupFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn bubble_inline_sizes(&mut self) {
|
||||
let _scope = layout_debug_scope!(
|
||||
"table_colgroup::bubble_inline_sizes {:x}",
|
||||
self.base.debug_id()
|
||||
);
|
||||
|
||||
for fragment in &self.cols {
|
||||
// Retrieve the specified value from the appropriate CSS property.
|
||||
let inline_size = fragment.style().content_inline_size();
|
||||
for _ in 0..fragment.column_span() {
|
||||
self.inline_sizes.push(inline_size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Table column inline-sizes are assigned in the table flow and propagated to table row flows
|
||||
/// and/or rowgroup flows. Therefore, table colgroup flows do not need to assign inline-sizes.
|
||||
fn assign_inline_sizes(&mut self, _: &LayoutContext) {}
|
||||
|
||||
/// Table columns do not have block-size.
|
||||
fn assign_block_size(&mut self, _: &LayoutContext) {}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {}
|
||||
|
||||
fn update_late_computed_block_position_if_necessary(&mut self, _: Au) {}
|
||||
|
||||
// Table columns are invisible.
|
||||
fn build_display_list(&mut self, _: &mut DisplayListBuildState) {}
|
||||
|
||||
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
|
||||
self.base.stacking_context_id = state.current_stacking_context_id;
|
||||
self.base.clipping_and_scrolling = Some(state.current_clipping_and_scrolling);
|
||||
}
|
||||
|
||||
fn repair_style(&mut self, _: &crate::ServoArc<ComputedValues>) {}
|
||||
|
||||
fn compute_overflow(&self) -> Overflow {
|
||||
Overflow::new()
|
||||
}
|
||||
|
||||
fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au> {
|
||||
panic!("Table column groups can't be containing blocks!")
|
||||
}
|
||||
|
||||
fn iterate_through_fragment_border_boxes(
|
||||
&self,
|
||||
_: &mut dyn FragmentBorderBoxIterator,
|
||||
_: i32,
|
||||
_: &Point2D<Au>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn mutate_fragments(&mut self, _: &mut dyn FnMut(&mut Fragment)) {}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TableColGroupFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.fragment {
|
||||
Some(ref rb) => write!(f, "TableColGroupFlow: {:?}", rb),
|
||||
None => write!(f, "TableColGroupFlow"),
|
||||
}
|
||||
}
|
||||
}
|
1158
components/layout_2020/table_row.rs
Normal file
1158
components/layout_2020/table_row.rs
Normal file
File diff suppressed because it is too large
Load diff
274
components/layout_2020/table_rowgroup.rs
Normal file
274
components/layout_2020/table_rowgroup.rs
Normal file
|
@ -0,0 +1,274 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS table formatting contexts.
|
||||
|
||||
use crate::block::{BlockFlow, ISizeAndMarginsComputer};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::{
|
||||
DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
|
||||
};
|
||||
use crate::flow::{Flow, FlowClass, OpaqueFlow};
|
||||
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
|
||||
use crate::layout_debug;
|
||||
use crate::table::{ColumnIntrinsicInlineSize, InternalTable, TableLikeFlow};
|
||||
use app_units::Au;
|
||||
use euclid::Point2D;
|
||||
use gfx_traits::print_tree::PrintTree;
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::fmt;
|
||||
use std::iter::{IntoIterator, Iterator, Peekable};
|
||||
use style::computed_values::{border_collapse, border_spacing};
|
||||
use style::logical_geometry::LogicalSize;
|
||||
use style::properties::ComputedValues;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl crate::flow::HasBaseFlow for TableRowGroupFlow {}
|
||||
|
||||
/// A table formatting context.
|
||||
#[repr(C)]
|
||||
pub struct TableRowGroupFlow {
|
||||
/// Fields common to all block flows.
|
||||
pub block_flow: BlockFlow,
|
||||
|
||||
/// Information about the intrinsic inline-sizes of each column.
|
||||
pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
|
||||
|
||||
/// The spacing for this rowgroup.
|
||||
pub spacing: border_spacing::T,
|
||||
|
||||
/// The final width of the borders in the inline direction for each cell, computed by the
|
||||
/// entire table and pushed down into each row during inline size computation.
|
||||
pub collapsed_inline_direction_border_widths_for_table: Vec<Au>,
|
||||
|
||||
/// The final width of the borders in the block direction for each cell, computed by the
|
||||
/// entire table and pushed down into each row during inline size computation.
|
||||
pub collapsed_block_direction_border_widths_for_table: Vec<Au>,
|
||||
}
|
||||
|
||||
impl Serialize for TableRowGroupFlow {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
self.block_flow.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowGroupFlow {
|
||||
pub fn from_fragment(fragment: Fragment) -> TableRowGroupFlow {
|
||||
TableRowGroupFlow {
|
||||
block_flow: BlockFlow::from_fragment(fragment),
|
||||
column_intrinsic_inline_sizes: Vec::new(),
|
||||
spacing: border_spacing::T::zero(),
|
||||
collapsed_inline_direction_border_widths_for_table: Vec::new(),
|
||||
collapsed_block_direction_border_widths_for_table: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn populate_collapsed_border_spacing<'a, I>(
|
||||
&mut self,
|
||||
collapsed_inline_direction_border_widths_for_table: &[Au],
|
||||
collapsed_block_direction_border_widths_for_table: &mut Peekable<I>,
|
||||
) where
|
||||
I: Iterator<Item = &'a Au>,
|
||||
{
|
||||
self.collapsed_inline_direction_border_widths_for_table
|
||||
.clear();
|
||||
self.collapsed_inline_direction_border_widths_for_table
|
||||
.extend(
|
||||
collapsed_inline_direction_border_widths_for_table
|
||||
.into_iter()
|
||||
.map(|x| *x),
|
||||
);
|
||||
|
||||
for _ in 0..self.block_flow.base.children.len() {
|
||||
if let Some(collapsed_block_direction_border_width_for_table) =
|
||||
collapsed_block_direction_border_widths_for_table.next()
|
||||
{
|
||||
self.collapsed_block_direction_border_widths_for_table
|
||||
.push(*collapsed_block_direction_border_width_for_table)
|
||||
}
|
||||
}
|
||||
if let Some(collapsed_block_direction_border_width_for_table) =
|
||||
collapsed_block_direction_border_widths_for_table.peek()
|
||||
{
|
||||
self.collapsed_block_direction_border_widths_for_table
|
||||
.push(**collapsed_block_direction_border_width_for_table)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableRowGroupFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
FlowClass::TableRowGroup
|
||||
}
|
||||
|
||||
fn as_mut_table_rowgroup(&mut self) -> &mut TableRowGroupFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_table_rowgroup(&self) -> &TableRowGroupFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_block(&mut self) -> &mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
fn as_block(&self) -> &BlockFlow {
|
||||
&self.block_flow
|
||||
}
|
||||
|
||||
fn bubble_inline_sizes(&mut self) {
|
||||
let _scope = layout_debug_scope!(
|
||||
"table_rowgroup::bubble_inline_sizes {:x}",
|
||||
self.block_flow.base.debug_id()
|
||||
);
|
||||
// Proper calculation of intrinsic sizes in table layout requires access to the entire
|
||||
// table, which we don't have yet. Defer to our parent.
|
||||
}
|
||||
|
||||
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
|
||||
/// When called on this context, the context has had its inline-size set by the parent context.
|
||||
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
let _scope = layout_debug_scope!(
|
||||
"table_rowgroup::assign_inline_sizes {:x}",
|
||||
self.block_flow.base.debug_id()
|
||||
);
|
||||
debug!(
|
||||
"assign_inline_sizes({}): assigning inline_size for flow",
|
||||
"table_rowgroup"
|
||||
);
|
||||
|
||||
let shared_context = layout_context.shared_context();
|
||||
// The position was set to the containing block by the flow's parent.
|
||||
let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
|
||||
let (inline_start_content_edge, inline_end_content_edge) = (Au(0), Au(0));
|
||||
let content_inline_size = containing_block_inline_size;
|
||||
|
||||
let border_collapse = self
|
||||
.block_flow
|
||||
.fragment
|
||||
.style
|
||||
.get_inherited_table()
|
||||
.border_collapse;
|
||||
let inline_size_computer = InternalTable;
|
||||
inline_size_computer.compute_used_inline_size(
|
||||
&mut self.block_flow,
|
||||
shared_context,
|
||||
containing_block_inline_size,
|
||||
);
|
||||
|
||||
let collapsed_inline_direction_border_widths_for_table =
|
||||
&self.collapsed_inline_direction_border_widths_for_table;
|
||||
let mut collapsed_block_direction_border_widths_for_table = self
|
||||
.collapsed_block_direction_border_widths_for_table
|
||||
.iter()
|
||||
.peekable();
|
||||
self.block_flow.propagate_assigned_inline_size_to_children(
|
||||
shared_context,
|
||||
inline_start_content_edge,
|
||||
inline_end_content_edge,
|
||||
content_inline_size,
|
||||
|child_flow,
|
||||
_child_index,
|
||||
_content_inline_size,
|
||||
_writing_mode,
|
||||
_inline_start_margin_edge,
|
||||
_inline_end_margin_edge| {
|
||||
if border_collapse == border_collapse::T::Collapse {
|
||||
let child_table_row = child_flow.as_mut_table_row();
|
||||
child_table_row.populate_collapsed_border_spacing(
|
||||
collapsed_inline_direction_border_widths_for_table,
|
||||
&mut collapsed_block_direction_border_widths_for_table,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn assign_block_size(&mut self, lc: &LayoutContext) {
|
||||
debug!("assign_block_size: assigning block_size for table_rowgroup");
|
||||
self.block_flow
|
||||
.assign_block_size_for_table_like_flow(self.spacing.vertical(), lc);
|
||||
}
|
||||
|
||||
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow
|
||||
.compute_stacking_relative_position(layout_context)
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_inline_position_if_necessary(inline_position)
|
||||
}
|
||||
|
||||
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_block_position_if_necessary(block_position)
|
||||
}
|
||||
|
||||
fn build_display_list(&mut self, _: &mut DisplayListBuildState) {
|
||||
use style::servo::restyle_damage::ServoRestyleDamage;
|
||||
|
||||
// we skip setting the damage in TableCellStyleInfo::build_display_list()
|
||||
// because we only have immutable access
|
||||
self.block_flow
|
||||
.fragment
|
||||
.restyle_damage
|
||||
.remove(ServoRestyleDamage::REPAINT);
|
||||
}
|
||||
|
||||
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
|
||||
self.block_flow.collect_stacking_contexts_for_block(
|
||||
state,
|
||||
StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK,
|
||||
);
|
||||
}
|
||||
|
||||
fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
|
||||
self.block_flow.repair_style(new_style)
|
||||
}
|
||||
|
||||
fn compute_overflow(&self) -> Overflow {
|
||||
self.block_flow.compute_overflow()
|
||||
}
|
||||
|
||||
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
|
||||
self.block_flow.contains_roots_of_absolute_flow_tree()
|
||||
}
|
||||
|
||||
fn is_absolute_containing_block(&self) -> bool {
|
||||
self.block_flow.is_absolute_containing_block()
|
||||
}
|
||||
|
||||
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||
self.block_flow.generated_containing_block_size(flow)
|
||||
}
|
||||
|
||||
fn iterate_through_fragment_border_boxes(
|
||||
&self,
|
||||
iterator: &mut dyn FragmentBorderBoxIterator,
|
||||
level: i32,
|
||||
stacking_context_position: &Point2D<Au>,
|
||||
) {
|
||||
self.block_flow.iterate_through_fragment_border_boxes(
|
||||
iterator,
|
||||
level,
|
||||
stacking_context_position,
|
||||
)
|
||||
}
|
||||
|
||||
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
|
||||
self.block_flow.mutate_fragments(mutator)
|
||||
}
|
||||
|
||||
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
|
||||
self.block_flow.print_extra_flow_children(print_tree);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TableRowGroupFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TableRowGroupFlow: {:?}", self.block_flow)
|
||||
}
|
||||
}
|
996
components/layout_2020/table_wrapper.rs
Normal file
996
components/layout_2020/table_wrapper.rs
Normal file
|
@ -0,0 +1,996 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS tables.
|
||||
//!
|
||||
//! This follows the "More Precise Definitions of Inline Layout and Table Layout" proposal written
|
||||
//! by L. David Baron (Mozilla) here:
|
||||
//!
|
||||
//! http://dbaron.org/css/intrinsic/
|
||||
//!
|
||||
//! Hereafter this document is referred to as INTRINSIC.
|
||||
|
||||
use crate::block::{
|
||||
AbsoluteNonReplaced, BlockFlow, FloatNonReplaced, ISizeAndMarginsComputer, ISizeConstraintInput,
|
||||
};
|
||||
use crate::block::{ISizeConstraintSolution, MarginsMayCollapseFlag};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::StackingContextCollectionState;
|
||||
use crate::display_list::{DisplayListBuildState, StackingContextCollectionFlags};
|
||||
use crate::floats::FloatKind;
|
||||
use crate::flow::{Flow, FlowClass, FlowFlags, ImmutableFlowUtils, OpaqueFlow};
|
||||
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
|
||||
use crate::model::MaybeAuto;
|
||||
use crate::table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize};
|
||||
use app_units::Au;
|
||||
use euclid::Point2D;
|
||||
use gfx_traits::print_tree::PrintTree;
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt;
|
||||
use std::ops::Add;
|
||||
use style::computed_values::{position, table_layout};
|
||||
use style::context::SharedStyleContext;
|
||||
use style::logical_geometry::{LogicalRect, LogicalSize};
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::Size;
|
||||
use style::values::CSSFloat;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
pub enum TableLayout {
|
||||
Fixed,
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl crate::flow::HasBaseFlow for TableWrapperFlow {}
|
||||
|
||||
/// A table wrapper flow based on a block formatting context.
|
||||
#[derive(Serialize)]
|
||||
#[repr(C)]
|
||||
pub struct TableWrapperFlow {
|
||||
pub block_flow: BlockFlow,
|
||||
|
||||
/// Intrinsic column inline sizes according to INTRINSIC § 4.1
|
||||
pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
|
||||
|
||||
/// Table-layout property
|
||||
pub table_layout: TableLayout,
|
||||
}
|
||||
|
||||
impl TableWrapperFlow {
|
||||
pub fn from_fragment(fragment: Fragment) -> TableWrapperFlow {
|
||||
TableWrapperFlow::from_fragment_and_float_kind(fragment, None)
|
||||
}
|
||||
|
||||
pub fn from_fragment_and_float_kind(
|
||||
fragment: Fragment,
|
||||
float_kind: Option<FloatKind>,
|
||||
) -> TableWrapperFlow {
|
||||
let mut block_flow = BlockFlow::from_fragment_and_float_kind(fragment, float_kind);
|
||||
let table_layout =
|
||||
if block_flow.fragment().style().get_table().table_layout == table_layout::T::Fixed {
|
||||
TableLayout::Fixed
|
||||
} else {
|
||||
TableLayout::Auto
|
||||
};
|
||||
TableWrapperFlow {
|
||||
block_flow: block_flow,
|
||||
column_intrinsic_inline_sizes: vec![],
|
||||
table_layout: table_layout,
|
||||
}
|
||||
}
|
||||
|
||||
fn border_padding_and_spacing(&mut self) -> (Au, Au) {
|
||||
let (mut table_border_padding, mut spacing) = (Au(0), Au(0));
|
||||
for kid in self.block_flow.base.child_iter_mut() {
|
||||
if kid.is_table() {
|
||||
let kid_table = kid.as_table();
|
||||
spacing = kid_table.total_horizontal_spacing();
|
||||
table_border_padding = kid_table
|
||||
.block_flow
|
||||
.fragment
|
||||
.border_padding
|
||||
.inline_start_end();
|
||||
break;
|
||||
}
|
||||
}
|
||||
(table_border_padding, spacing)
|
||||
}
|
||||
|
||||
// Instructs our first child, which is the table itself, to compute its border and padding.
|
||||
//
|
||||
// This is a little weird because we're computing border/padding/margins for our child,
|
||||
// when normally the child computes it itself. But it has to be this way because the
|
||||
// padding will affect where we place the child. This is an odd artifact of the way that
|
||||
// tables are separated into table flows and table wrapper flows.
|
||||
fn compute_border_and_padding_of_table(&mut self) {
|
||||
let available_inline_size = self.block_flow.base.block_container_inline_size;
|
||||
for kid in self.block_flow.base.child_iter_mut() {
|
||||
if !kid.is_table() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let kid_table = kid.as_mut_table();
|
||||
let kid_block_flow = &mut kid_table.block_flow;
|
||||
kid_block_flow
|
||||
.fragment
|
||||
.compute_border_and_padding(available_inline_size);
|
||||
kid_block_flow
|
||||
.fragment
|
||||
.compute_block_direction_margins(available_inline_size);
|
||||
kid_block_flow
|
||||
.fragment
|
||||
.compute_inline_direction_margins(available_inline_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates table column sizes for automatic layout per INTRINSIC § 4.3.
|
||||
fn calculate_table_column_sizes_for_automatic_layout(
|
||||
&mut self,
|
||||
intermediate_column_inline_sizes: &mut [IntermediateColumnInlineSize],
|
||||
) {
|
||||
let available_inline_size = self.available_inline_size();
|
||||
|
||||
// Compute all the guesses for the column sizes, and sum them.
|
||||
let mut total_guess = AutoLayoutCandidateGuess::new();
|
||||
let guesses: Vec<AutoLayoutCandidateGuess> = self
|
||||
.column_intrinsic_inline_sizes
|
||||
.iter()
|
||||
.map(|column_intrinsic_inline_size| {
|
||||
let guess = AutoLayoutCandidateGuess::from_column_intrinsic_inline_size(
|
||||
column_intrinsic_inline_size,
|
||||
available_inline_size,
|
||||
);
|
||||
total_guess = &total_guess + &guess;
|
||||
guess
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Assign inline sizes.
|
||||
let selection =
|
||||
SelectedAutoLayoutCandidateGuess::select(&total_guess, available_inline_size);
|
||||
let mut total_used_inline_size = Au(0);
|
||||
for (intermediate_column_inline_size, guess) in intermediate_column_inline_sizes
|
||||
.iter_mut()
|
||||
.zip(guesses.iter())
|
||||
{
|
||||
intermediate_column_inline_size.size = guess.calculate(selection);
|
||||
intermediate_column_inline_size.percentage = 0.0;
|
||||
total_used_inline_size = total_used_inline_size + intermediate_column_inline_size.size
|
||||
}
|
||||
|
||||
// Distribute excess inline-size if necessary per INTRINSIC § 4.4.
|
||||
//
|
||||
// FIXME(pcwalton, spec): How do I deal with fractional excess?
|
||||
let excess_inline_size = available_inline_size - total_used_inline_size;
|
||||
if excess_inline_size > Au(0) &&
|
||||
selection ==
|
||||
SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize
|
||||
{
|
||||
let mut info = ExcessInlineSizeDistributionInfo::new();
|
||||
for column_intrinsic_inline_size in &self.column_intrinsic_inline_sizes {
|
||||
info.update(column_intrinsic_inline_size)
|
||||
}
|
||||
|
||||
let mut total_distributed_excess_size = Au(0);
|
||||
for (intermediate_column_inline_size, column_intrinsic_inline_size) in
|
||||
intermediate_column_inline_sizes
|
||||
.iter_mut()
|
||||
.zip(self.column_intrinsic_inline_sizes.iter())
|
||||
{
|
||||
info.distribute_excess_inline_size_to_column(
|
||||
intermediate_column_inline_size,
|
||||
column_intrinsic_inline_size,
|
||||
excess_inline_size,
|
||||
&mut total_distributed_excess_size,
|
||||
)
|
||||
}
|
||||
total_used_inline_size = available_inline_size
|
||||
}
|
||||
|
||||
self.set_inline_size(total_used_inline_size)
|
||||
}
|
||||
|
||||
fn available_inline_size(&mut self) -> Au {
|
||||
let available_inline_size = self.block_flow.fragment.border_box.size.inline;
|
||||
let (table_border_padding, spacing) = self.border_padding_and_spacing();
|
||||
|
||||
// FIXME(pcwalton, spec): INTRINSIC § 8 does not properly define how to compute this, but
|
||||
// says "the basic idea is the same as the shrink-to-fit width that CSS2.1 defines". So we
|
||||
// just use the shrink-to-fit inline size.
|
||||
let available_inline_size = match self.block_flow.fragment.style().content_inline_size() {
|
||||
Size::Auto => {
|
||||
self.block_flow
|
||||
.get_shrink_to_fit_inline_size(available_inline_size) -
|
||||
table_border_padding
|
||||
},
|
||||
// FIXME(mttr): This fixes #4421 without breaking our current reftests, but I'm not
|
||||
// completely sure this is "correct".
|
||||
//
|
||||
// That said, `available_inline_size` is, as far as I can tell, equal to the table's
|
||||
// computed width property (W) and is used from this point forward in a way that seems
|
||||
// to correspond with CSS 2.1 § 17.5.2.2 under "Column and caption widths influence the
|
||||
// final table width as follows: …"
|
||||
_ => available_inline_size,
|
||||
};
|
||||
available_inline_size - spacing
|
||||
}
|
||||
|
||||
fn set_inline_size(&mut self, total_used_inline_size: Au) {
|
||||
let (table_border_padding, spacing) = self.border_padding_and_spacing();
|
||||
self.block_flow.fragment.border_box.size.inline =
|
||||
total_used_inline_size + table_border_padding + spacing;
|
||||
self.block_flow.base.position.size.inline = total_used_inline_size +
|
||||
table_border_padding +
|
||||
spacing +
|
||||
self.block_flow.fragment.margin.inline_start_end();
|
||||
|
||||
let writing_mode = self.block_flow.base.writing_mode;
|
||||
let container_mode = self.block_flow.base.block_container_writing_mode;
|
||||
|
||||
if writing_mode.is_bidi_ltr() != container_mode.is_bidi_ltr() {
|
||||
// If our "start" direction is different from our parent flow, then `border_box.start.i`
|
||||
// depends on `border_box.size.inline`.
|
||||
self.block_flow.fragment.border_box.start.i =
|
||||
self.block_flow.base.block_container_inline_size -
|
||||
self.block_flow.fragment.margin.inline_end -
|
||||
self.block_flow.fragment.border_box.size.inline;
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_used_inline_size(
|
||||
&mut self,
|
||||
shared_context: &SharedStyleContext,
|
||||
parent_flow_inline_size: Au,
|
||||
intermediate_column_inline_sizes: &[IntermediateColumnInlineSize],
|
||||
) {
|
||||
let (border_padding, spacing) = self.border_padding_and_spacing();
|
||||
let minimum_width_of_all_columns = intermediate_column_inline_sizes.iter().fold(
|
||||
border_padding + spacing,
|
||||
|accumulator, intermediate_column_inline_sizes| {
|
||||
accumulator + intermediate_column_inline_sizes.size
|
||||
},
|
||||
);
|
||||
let preferred_width_of_all_columns = self.column_intrinsic_inline_sizes.iter().fold(
|
||||
border_padding + spacing,
|
||||
|accumulator, column_intrinsic_inline_sizes| {
|
||||
accumulator + column_intrinsic_inline_sizes.preferred
|
||||
},
|
||||
);
|
||||
|
||||
// Delegate to the appropriate inline size computer to find the constraint inputs and write
|
||||
// the constraint solutions in.
|
||||
if self.block_flow.base.flags.is_float() {
|
||||
let inline_size_computer = FloatedTable {
|
||||
minimum_width_of_all_columns: minimum_width_of_all_columns,
|
||||
preferred_width_of_all_columns: preferred_width_of_all_columns,
|
||||
table_border_padding: border_padding,
|
||||
};
|
||||
let input = inline_size_computer.compute_inline_size_constraint_inputs(
|
||||
&mut self.block_flow,
|
||||
parent_flow_inline_size,
|
||||
shared_context,
|
||||
);
|
||||
|
||||
let solution =
|
||||
inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input);
|
||||
inline_size_computer
|
||||
.set_inline_size_constraint_solutions(&mut self.block_flow, solution);
|
||||
inline_size_computer
|
||||
.set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution);
|
||||
return;
|
||||
}
|
||||
|
||||
if !self
|
||||
.block_flow
|
||||
.base
|
||||
.flags
|
||||
.contains(FlowFlags::INLINE_POSITION_IS_STATIC)
|
||||
{
|
||||
let inline_size_computer = AbsoluteTable {
|
||||
minimum_width_of_all_columns: minimum_width_of_all_columns,
|
||||
preferred_width_of_all_columns: preferred_width_of_all_columns,
|
||||
table_border_padding: border_padding,
|
||||
};
|
||||
let input = inline_size_computer.compute_inline_size_constraint_inputs(
|
||||
&mut self.block_flow,
|
||||
parent_flow_inline_size,
|
||||
shared_context,
|
||||
);
|
||||
|
||||
let solution =
|
||||
inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input);
|
||||
inline_size_computer
|
||||
.set_inline_size_constraint_solutions(&mut self.block_flow, solution);
|
||||
inline_size_computer
|
||||
.set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution);
|
||||
return;
|
||||
}
|
||||
|
||||
let inline_size_computer = Table {
|
||||
minimum_width_of_all_columns: minimum_width_of_all_columns,
|
||||
preferred_width_of_all_columns: preferred_width_of_all_columns,
|
||||
table_border_padding: border_padding,
|
||||
};
|
||||
let input = inline_size_computer.compute_inline_size_constraint_inputs(
|
||||
&mut self.block_flow,
|
||||
parent_flow_inline_size,
|
||||
shared_context,
|
||||
);
|
||||
|
||||
let solution =
|
||||
inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input);
|
||||
inline_size_computer.set_inline_size_constraint_solutions(&mut self.block_flow, solution);
|
||||
inline_size_computer
|
||||
.set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution);
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableWrapperFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
FlowClass::TableWrapper
|
||||
}
|
||||
|
||||
fn as_table_wrapper(&self) -> &TableWrapperFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_block(&mut self) -> &mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
fn as_block(&self) -> &BlockFlow {
|
||||
&self.block_flow
|
||||
}
|
||||
|
||||
fn mark_as_root(&mut self) {
|
||||
self.block_flow.mark_as_root();
|
||||
}
|
||||
|
||||
fn bubble_inline_sizes(&mut self) {
|
||||
// Get the intrinsic column inline-sizes info from the table flow.
|
||||
for kid in self.block_flow.base.child_iter_mut() {
|
||||
debug_assert!(kid.is_table_caption() || kid.is_table());
|
||||
if kid.is_table() {
|
||||
let table = kid.as_table();
|
||||
self.column_intrinsic_inline_sizes = table.column_intrinsic_inline_sizes.clone();
|
||||
}
|
||||
}
|
||||
|
||||
self.block_flow.bubble_inline_sizes();
|
||||
}
|
||||
|
||||
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
debug!(
|
||||
"assign_inline_sizes({}): assigning inline_size for flow",
|
||||
if self.block_flow.base.flags.is_float() {
|
||||
"floated table_wrapper"
|
||||
} else {
|
||||
"table_wrapper"
|
||||
}
|
||||
);
|
||||
|
||||
let shared_context = layout_context.shared_context();
|
||||
self.block_flow
|
||||
.initialize_container_size_for_root(shared_context);
|
||||
|
||||
let mut intermediate_column_inline_sizes = self
|
||||
.column_intrinsic_inline_sizes
|
||||
.iter()
|
||||
.map(
|
||||
|column_intrinsic_inline_size| IntermediateColumnInlineSize {
|
||||
size: column_intrinsic_inline_size.minimum_length,
|
||||
percentage: column_intrinsic_inline_size.percentage,
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Our inline-size was set to the inline-size of the containing block by the flow's parent.
|
||||
// Now compute the real value.
|
||||
let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
|
||||
if self.block_flow.base.flags.is_float() {
|
||||
self.block_flow
|
||||
.float
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.containing_inline_size = containing_block_inline_size;
|
||||
}
|
||||
|
||||
// This has to be done before computing our inline size because `compute_used_inline_size`
|
||||
// internally consults the border and padding of the table.
|
||||
self.compute_border_and_padding_of_table();
|
||||
|
||||
self.compute_used_inline_size(
|
||||
shared_context,
|
||||
containing_block_inline_size,
|
||||
&intermediate_column_inline_sizes,
|
||||
);
|
||||
|
||||
match self.table_layout {
|
||||
TableLayout::Auto => self.calculate_table_column_sizes_for_automatic_layout(
|
||||
&mut intermediate_column_inline_sizes,
|
||||
),
|
||||
TableLayout::Fixed => {},
|
||||
}
|
||||
|
||||
let inline_start_content_edge = self.block_flow.fragment.border_box.start.i;
|
||||
let content_inline_size = self.block_flow.fragment.border_box.size.inline;
|
||||
let inline_end_content_edge = self.block_flow.fragment.border_padding.inline_end +
|
||||
self.block_flow.fragment.margin.inline_end;
|
||||
|
||||
// In case of fixed layout, column inline-sizes are calculated in table flow.
|
||||
let assigned_column_inline_sizes = match self.table_layout {
|
||||
TableLayout::Fixed => None,
|
||||
TableLayout::Auto => Some(
|
||||
intermediate_column_inline_sizes
|
||||
.iter()
|
||||
.map(|sizes| ColumnComputedInlineSize { size: sizes.size })
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
};
|
||||
|
||||
match assigned_column_inline_sizes {
|
||||
None => self.block_flow.propagate_assigned_inline_size_to_children(
|
||||
shared_context,
|
||||
inline_start_content_edge,
|
||||
inline_end_content_edge,
|
||||
content_inline_size,
|
||||
|_, _, _, _, _, _| {},
|
||||
),
|
||||
Some(ref assigned_column_inline_sizes) => {
|
||||
self.block_flow.propagate_assigned_inline_size_to_children(
|
||||
shared_context,
|
||||
inline_start_content_edge,
|
||||
inline_end_content_edge,
|
||||
content_inline_size,
|
||||
|child_flow, _, _, _, _, _| {
|
||||
if child_flow.class() == FlowClass::Table {
|
||||
child_flow.as_mut_table().column_computed_inline_sizes =
|
||||
assigned_column_inline_sizes.to_vec();
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_block_size(&mut self, layout_context: &LayoutContext) {
|
||||
debug!("assign_block_size: assigning block_size for table_wrapper");
|
||||
let remaining = self.block_flow.assign_block_size_block_base(
|
||||
layout_context,
|
||||
None,
|
||||
MarginsMayCollapseFlag::MarginsMayNotCollapse,
|
||||
);
|
||||
debug_assert!(remaining.is_none());
|
||||
}
|
||||
|
||||
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow
|
||||
.compute_stacking_relative_position(layout_context)
|
||||
}
|
||||
|
||||
fn place_float_if_applicable<'a>(&mut self) {
|
||||
self.block_flow.place_float_if_applicable()
|
||||
}
|
||||
|
||||
fn assign_block_size_for_inorder_child_if_necessary(
|
||||
&mut self,
|
||||
layout_context: &LayoutContext,
|
||||
parent_thread_id: u8,
|
||||
content_box: LogicalRect<Au>,
|
||||
) -> bool {
|
||||
self.block_flow
|
||||
.assign_block_size_for_inorder_child_if_necessary(
|
||||
layout_context,
|
||||
parent_thread_id,
|
||||
content_box,
|
||||
)
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_inline_position_if_necessary(inline_position)
|
||||
}
|
||||
|
||||
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
|
||||
self.block_flow
|
||||
.update_late_computed_block_position_if_necessary(block_position)
|
||||
}
|
||||
|
||||
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||
self.block_flow.generated_containing_block_size(flow)
|
||||
}
|
||||
|
||||
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
|
||||
self.block_flow.build_display_list(state);
|
||||
}
|
||||
|
||||
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
|
||||
self.block_flow.collect_stacking_contexts_for_block(
|
||||
state,
|
||||
StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK |
|
||||
StackingContextCollectionFlags::NEVER_CREATES_CLIP_SCROLL_NODE,
|
||||
);
|
||||
}
|
||||
|
||||
fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
|
||||
self.block_flow.repair_style(new_style)
|
||||
}
|
||||
|
||||
fn compute_overflow(&self) -> Overflow {
|
||||
self.block_flow.compute_overflow()
|
||||
}
|
||||
|
||||
fn iterate_through_fragment_border_boxes(
|
||||
&self,
|
||||
iterator: &mut dyn FragmentBorderBoxIterator,
|
||||
level: i32,
|
||||
stacking_context_position: &Point2D<Au>,
|
||||
) {
|
||||
self.block_flow.iterate_through_fragment_border_boxes(
|
||||
iterator,
|
||||
level,
|
||||
stacking_context_position,
|
||||
)
|
||||
}
|
||||
|
||||
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
|
||||
self.block_flow.mutate_fragments(mutator)
|
||||
}
|
||||
|
||||
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
|
||||
self.block_flow.print_extra_flow_children(print_tree);
|
||||
}
|
||||
|
||||
fn positioning(&self) -> position::T {
|
||||
self.block_flow.positioning()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TableWrapperFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.block_flow.base.flags.is_float() {
|
||||
write!(f, "TableWrapperFlow(Float): {:?}", self.block_flow)
|
||||
} else {
|
||||
write!(f, "TableWrapperFlow: {:?}", self.block_flow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The layout "guesses" defined in INTRINSIC § 4.3.
|
||||
struct AutoLayoutCandidateGuess {
|
||||
/// The column inline-size assignment where each column is assigned its intrinsic minimum
|
||||
/// inline-size.
|
||||
minimum_guess: Au,
|
||||
|
||||
/// The column inline-size assignment where:
|
||||
/// * A column with an intrinsic percentage inline-size greater than 0% is assigned the
|
||||
/// larger of:
|
||||
/// - Its intrinsic percentage inline-size times the assignable inline-size;
|
||||
/// - Its intrinsic minimum inline-size;
|
||||
/// * Other columns receive their intrinsic minimum inline-size.
|
||||
minimum_percentage_guess: Au,
|
||||
|
||||
/// The column inline-size assignment where:
|
||||
/// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the
|
||||
/// larger of:
|
||||
/// - Its intrinsic percentage inline-size times the assignable inline-size;
|
||||
/// - Its intrinsic minimum inline-size;
|
||||
/// * Any other column that is constrained is assigned its intrinsic preferred inline-size;
|
||||
/// * Other columns are assigned their intrinsic minimum inline-size.
|
||||
minimum_specified_guess: Au,
|
||||
|
||||
/// The column inline-size assignment where:
|
||||
/// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the
|
||||
/// larger of:
|
||||
/// - Its intrinsic percentage inline-size times the assignable inline-size;
|
||||
/// - Its intrinsic minimum inline-size;
|
||||
/// * Other columns are assigned their intrinsic preferred inline-size.
|
||||
preferred_guess: Au,
|
||||
}
|
||||
|
||||
impl AutoLayoutCandidateGuess {
|
||||
/// Creates a guess with all elements initialized to zero.
|
||||
fn new() -> AutoLayoutCandidateGuess {
|
||||
AutoLayoutCandidateGuess {
|
||||
minimum_guess: Au(0),
|
||||
minimum_percentage_guess: Au(0),
|
||||
minimum_specified_guess: Au(0),
|
||||
preferred_guess: Au(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills in the inline-size guesses for this column per INTRINSIC § 4.3.
|
||||
fn from_column_intrinsic_inline_size(
|
||||
column_intrinsic_inline_size: &ColumnIntrinsicInlineSize,
|
||||
assignable_inline_size: Au,
|
||||
) -> AutoLayoutCandidateGuess {
|
||||
let minimum_percentage_guess = max(
|
||||
assignable_inline_size.scale_by(column_intrinsic_inline_size.percentage),
|
||||
column_intrinsic_inline_size.minimum_length,
|
||||
);
|
||||
AutoLayoutCandidateGuess {
|
||||
minimum_guess: column_intrinsic_inline_size.minimum_length,
|
||||
minimum_percentage_guess: minimum_percentage_guess,
|
||||
// FIXME(pcwalton): We need the notion of *constrainedness* per INTRINSIC § 4 to
|
||||
// implement this one correctly.
|
||||
minimum_specified_guess: if column_intrinsic_inline_size.percentage > 0.0 {
|
||||
minimum_percentage_guess
|
||||
} else if column_intrinsic_inline_size.constrained {
|
||||
column_intrinsic_inline_size.preferred
|
||||
} else {
|
||||
column_intrinsic_inline_size.minimum_length
|
||||
},
|
||||
preferred_guess: if column_intrinsic_inline_size.percentage > 0.0 {
|
||||
minimum_percentage_guess
|
||||
} else {
|
||||
column_intrinsic_inline_size.preferred
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the inline-size, interpolating appropriately based on the value of `selection`.
|
||||
///
|
||||
/// This does *not* distribute excess inline-size. That must be done later if necessary.
|
||||
fn calculate(&self, selection: SelectedAutoLayoutCandidateGuess) -> Au {
|
||||
match selection {
|
||||
SelectedAutoLayoutCandidateGuess::UseMinimumGuess => self.minimum_guess,
|
||||
SelectedAutoLayoutCandidateGuess::
|
||||
InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight) => {
|
||||
interp(self.minimum_guess, self.minimum_percentage_guess, weight)
|
||||
}
|
||||
SelectedAutoLayoutCandidateGuess::
|
||||
InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight) => {
|
||||
interp(self.minimum_percentage_guess, self.minimum_specified_guess, weight)
|
||||
}
|
||||
SelectedAutoLayoutCandidateGuess::
|
||||
InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight) => {
|
||||
interp(self.minimum_specified_guess, self.preferred_guess, weight)
|
||||
}
|
||||
SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize => {
|
||||
self.preferred_guess
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Add for &'a AutoLayoutCandidateGuess {
|
||||
type Output = AutoLayoutCandidateGuess;
|
||||
#[inline]
|
||||
fn add(self, other: &AutoLayoutCandidateGuess) -> AutoLayoutCandidateGuess {
|
||||
AutoLayoutCandidateGuess {
|
||||
minimum_guess: self.minimum_guess + other.minimum_guess,
|
||||
minimum_percentage_guess: self.minimum_percentage_guess +
|
||||
other.minimum_percentage_guess,
|
||||
minimum_specified_guess: self.minimum_specified_guess + other.minimum_specified_guess,
|
||||
preferred_guess: self.preferred_guess + other.preferred_guess,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The `CSSFloat` member specifies the weight of the smaller of the two guesses, on a scale from
|
||||
/// 0.0 to 1.0.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum SelectedAutoLayoutCandidateGuess {
|
||||
UseMinimumGuess,
|
||||
InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(CSSFloat),
|
||||
InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(CSSFloat),
|
||||
InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(CSSFloat),
|
||||
UsePreferredGuessAndDistributeExcessInlineSize,
|
||||
}
|
||||
|
||||
impl SelectedAutoLayoutCandidateGuess {
|
||||
/// See INTRINSIC § 4.3.
|
||||
///
|
||||
/// FIXME(pcwalton, INTRINSIC spec): INTRINSIC doesn't specify whether these are exclusive or
|
||||
/// inclusive ranges.
|
||||
fn select(
|
||||
guess: &AutoLayoutCandidateGuess,
|
||||
assignable_inline_size: Au,
|
||||
) -> SelectedAutoLayoutCandidateGuess {
|
||||
if assignable_inline_size < guess.minimum_guess {
|
||||
SelectedAutoLayoutCandidateGuess::UseMinimumGuess
|
||||
} else if assignable_inline_size < guess.minimum_percentage_guess {
|
||||
let weight = weight(
|
||||
guess.minimum_guess,
|
||||
assignable_inline_size,
|
||||
guess.minimum_percentage_guess,
|
||||
);
|
||||
SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight)
|
||||
} else if assignable_inline_size < guess.minimum_specified_guess {
|
||||
let weight = weight(
|
||||
guess.minimum_percentage_guess,
|
||||
assignable_inline_size,
|
||||
guess.minimum_specified_guess,
|
||||
);
|
||||
SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight)
|
||||
} else if assignable_inline_size < guess.preferred_guess {
|
||||
let weight = weight(
|
||||
guess.minimum_specified_guess,
|
||||
assignable_inline_size,
|
||||
guess.preferred_guess,
|
||||
);
|
||||
SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight)
|
||||
} else {
|
||||
SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the weight needed to linearly interpolate `middle` between two guesses `low` and
|
||||
/// `high` as specified by INTRINSIC § 4.3.
|
||||
fn weight(low: Au, middle: Au, high: Au) -> CSSFloat {
|
||||
(middle - low).to_f32_px() / (high - low).to_f32_px()
|
||||
}
|
||||
|
||||
/// Linearly interpolates between two guesses, as specified by INTRINSIC § 4.3.
|
||||
fn interp(low: Au, high: Au, weight: CSSFloat) -> Au {
|
||||
low + (high - low).scale_by(weight)
|
||||
}
|
||||
|
||||
struct ExcessInlineSizeDistributionInfo {
|
||||
preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au,
|
||||
count_of_nonconstrained_columns_with_no_percentage: u32,
|
||||
preferred_inline_size_of_constrained_columns_with_no_percentage: Au,
|
||||
total_percentage: CSSFloat,
|
||||
column_count: u32,
|
||||
}
|
||||
|
||||
impl ExcessInlineSizeDistributionInfo {
|
||||
fn new() -> ExcessInlineSizeDistributionInfo {
|
||||
ExcessInlineSizeDistributionInfo {
|
||||
preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au(0),
|
||||
count_of_nonconstrained_columns_with_no_percentage: 0,
|
||||
preferred_inline_size_of_constrained_columns_with_no_percentage: Au(0),
|
||||
total_percentage: 0.0,
|
||||
column_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, column_intrinsic_inline_size: &ColumnIntrinsicInlineSize) {
|
||||
if !column_intrinsic_inline_size.constrained &&
|
||||
column_intrinsic_inline_size.percentage == 0.0
|
||||
{
|
||||
self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage = self
|
||||
.preferred_inline_size_of_nonconstrained_columns_with_no_percentage +
|
||||
column_intrinsic_inline_size.preferred;
|
||||
self.count_of_nonconstrained_columns_with_no_percentage += 1
|
||||
}
|
||||
if column_intrinsic_inline_size.constrained &&
|
||||
column_intrinsic_inline_size.percentage == 0.0
|
||||
{
|
||||
self.preferred_inline_size_of_constrained_columns_with_no_percentage = self
|
||||
.preferred_inline_size_of_constrained_columns_with_no_percentage +
|
||||
column_intrinsic_inline_size.preferred
|
||||
}
|
||||
self.total_percentage += column_intrinsic_inline_size.percentage;
|
||||
self.column_count += 1
|
||||
}
|
||||
|
||||
/// Based on the information here, distributes excess inline-size to the given column per
|
||||
/// INTRINSIC § 4.4.
|
||||
///
|
||||
/// `#[inline]` so the compiler will hoist out the branch, which is loop-invariant.
|
||||
#[inline]
|
||||
fn distribute_excess_inline_size_to_column(
|
||||
&self,
|
||||
intermediate_column_inline_size: &mut IntermediateColumnInlineSize,
|
||||
column_intrinsic_inline_size: &ColumnIntrinsicInlineSize,
|
||||
excess_inline_size: Au,
|
||||
total_distributed_excess_size: &mut Au,
|
||||
) {
|
||||
let proportion =
|
||||
if self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage > Au(0) {
|
||||
// FIXME(spec, pcwalton): Gecko and WebKit do *something* here when there are
|
||||
// nonconstrained columns with no percentage *and* no preferred width. What do they
|
||||
// do?
|
||||
if !column_intrinsic_inline_size.constrained &&
|
||||
column_intrinsic_inline_size.percentage == 0.0
|
||||
{
|
||||
column_intrinsic_inline_size.preferred.to_f32_px() /
|
||||
self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage
|
||||
.to_f32_px()
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
} else if self.count_of_nonconstrained_columns_with_no_percentage > 0 {
|
||||
1.0 / (self.count_of_nonconstrained_columns_with_no_percentage as CSSFloat)
|
||||
} else if self.preferred_inline_size_of_constrained_columns_with_no_percentage > Au(0) {
|
||||
column_intrinsic_inline_size.preferred.to_f32_px() /
|
||||
self.preferred_inline_size_of_constrained_columns_with_no_percentage
|
||||
.to_f32_px()
|
||||
} else if self.total_percentage > 0.0 {
|
||||
column_intrinsic_inline_size.percentage / self.total_percentage
|
||||
} else {
|
||||
1.0 / (self.column_count as CSSFloat)
|
||||
};
|
||||
|
||||
// The `min` here has the effect of throwing away fractional excess at the end of the
|
||||
// table.
|
||||
let amount_to_distribute = min(
|
||||
excess_inline_size.scale_by(proportion),
|
||||
excess_inline_size - *total_distributed_excess_size,
|
||||
);
|
||||
*total_distributed_excess_size = *total_distributed_excess_size + amount_to_distribute;
|
||||
intermediate_column_inline_size.size =
|
||||
intermediate_column_inline_size.size + amount_to_distribute
|
||||
}
|
||||
}
|
||||
|
||||
/// An intermediate column size assignment.
|
||||
struct IntermediateColumnInlineSize {
|
||||
size: Au,
|
||||
percentage: f32,
|
||||
}
|
||||
|
||||
/// Returns the computed inline size of the table wrapper represented by `block`.
|
||||
///
|
||||
/// `table_border_padding` is the sum of the sizes of all border and padding in the inline
|
||||
/// direction of the table contained within this table wrapper.
|
||||
fn initial_computed_inline_size(
|
||||
block: &mut BlockFlow,
|
||||
containing_block_inline_size: Au,
|
||||
minimum_width_of_all_columns: Au,
|
||||
preferred_width_of_all_columns: Au,
|
||||
table_border_padding: Au,
|
||||
) -> MaybeAuto {
|
||||
match block.fragment.style.content_inline_size() {
|
||||
Size::Auto => {
|
||||
if preferred_width_of_all_columns + table_border_padding <= containing_block_inline_size
|
||||
{
|
||||
MaybeAuto::Specified(preferred_width_of_all_columns + table_border_padding)
|
||||
} else if minimum_width_of_all_columns > containing_block_inline_size {
|
||||
MaybeAuto::Specified(minimum_width_of_all_columns)
|
||||
} else {
|
||||
MaybeAuto::Auto
|
||||
}
|
||||
},
|
||||
Size::LengthPercentage(ref lp) => {
|
||||
let used = lp.to_used_value(containing_block_inline_size);
|
||||
MaybeAuto::Specified(max(
|
||||
used - table_border_padding,
|
||||
minimum_width_of_all_columns,
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
struct Table {
|
||||
minimum_width_of_all_columns: Au,
|
||||
preferred_width_of_all_columns: Au,
|
||||
table_border_padding: Au,
|
||||
}
|
||||
|
||||
impl ISizeAndMarginsComputer for Table {
|
||||
fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
|
||||
block
|
||||
.fragment
|
||||
.compute_border_and_padding(containing_block_inline_size)
|
||||
}
|
||||
|
||||
fn initial_computed_inline_size(
|
||||
&self,
|
||||
block: &mut BlockFlow,
|
||||
parent_flow_inline_size: Au,
|
||||
shared_context: &SharedStyleContext,
|
||||
) -> MaybeAuto {
|
||||
let containing_block_inline_size =
|
||||
self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
|
||||
initial_computed_inline_size(
|
||||
block,
|
||||
containing_block_inline_size,
|
||||
self.minimum_width_of_all_columns,
|
||||
self.preferred_width_of_all_columns,
|
||||
self.table_border_padding,
|
||||
)
|
||||
}
|
||||
|
||||
fn solve_inline_size_constraints(
|
||||
&self,
|
||||
block: &mut BlockFlow,
|
||||
input: &ISizeConstraintInput,
|
||||
) -> ISizeConstraintSolution {
|
||||
self.solve_block_inline_size_constraints(block, input)
|
||||
}
|
||||
}
|
||||
|
||||
struct FloatedTable {
|
||||
minimum_width_of_all_columns: Au,
|
||||
preferred_width_of_all_columns: Au,
|
||||
table_border_padding: Au,
|
||||
}
|
||||
|
||||
impl ISizeAndMarginsComputer for FloatedTable {
|
||||
fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
|
||||
block
|
||||
.fragment
|
||||
.compute_border_and_padding(containing_block_inline_size)
|
||||
}
|
||||
|
||||
fn initial_computed_inline_size(
|
||||
&self,
|
||||
block: &mut BlockFlow,
|
||||
parent_flow_inline_size: Au,
|
||||
shared_context: &SharedStyleContext,
|
||||
) -> MaybeAuto {
|
||||
let containing_block_inline_size =
|
||||
self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
|
||||
initial_computed_inline_size(
|
||||
block,
|
||||
containing_block_inline_size,
|
||||
self.minimum_width_of_all_columns,
|
||||
self.preferred_width_of_all_columns,
|
||||
self.table_border_padding,
|
||||
)
|
||||
}
|
||||
|
||||
fn solve_inline_size_constraints(
|
||||
&self,
|
||||
block: &mut BlockFlow,
|
||||
input: &ISizeConstraintInput,
|
||||
) -> ISizeConstraintSolution {
|
||||
FloatNonReplaced.solve_inline_size_constraints(block, input)
|
||||
}
|
||||
}
|
||||
|
||||
struct AbsoluteTable {
|
||||
minimum_width_of_all_columns: Au,
|
||||
preferred_width_of_all_columns: Au,
|
||||
table_border_padding: Au,
|
||||
}
|
||||
|
||||
impl ISizeAndMarginsComputer for AbsoluteTable {
|
||||
fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
|
||||
block
|
||||
.fragment
|
||||
.compute_border_and_padding(containing_block_inline_size)
|
||||
}
|
||||
|
||||
fn initial_computed_inline_size(
|
||||
&self,
|
||||
block: &mut BlockFlow,
|
||||
parent_flow_inline_size: Au,
|
||||
shared_context: &SharedStyleContext,
|
||||
) -> MaybeAuto {
|
||||
let containing_block_inline_size =
|
||||
self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
|
||||
initial_computed_inline_size(
|
||||
block,
|
||||
containing_block_inline_size,
|
||||
self.minimum_width_of_all_columns,
|
||||
self.preferred_width_of_all_columns,
|
||||
self.table_border_padding,
|
||||
)
|
||||
}
|
||||
|
||||
fn containing_block_inline_size(
|
||||
&self,
|
||||
block: &mut BlockFlow,
|
||||
parent_flow_inline_size: Au,
|
||||
shared_context: &SharedStyleContext,
|
||||
) -> Au {
|
||||
AbsoluteNonReplaced.containing_block_inline_size(
|
||||
block,
|
||||
parent_flow_inline_size,
|
||||
shared_context,
|
||||
)
|
||||
}
|
||||
|
||||
fn solve_inline_size_constraints(
|
||||
&self,
|
||||
block: &mut BlockFlow,
|
||||
input: &ISizeConstraintInput,
|
||||
) -> ISizeConstraintSolution {
|
||||
AbsoluteNonReplaced.solve_inline_size_constraints(block, input)
|
||||
}
|
||||
|
||||
fn set_inline_position_of_flow_if_necessary(
|
||||
&self,
|
||||
block: &mut BlockFlow,
|
||||
solution: ISizeConstraintSolution,
|
||||
) {
|
||||
AbsoluteNonReplaced.set_inline_position_of_flow_if_necessary(block, solution);
|
||||
}
|
||||
}
|
18
components/layout_2020/tests/size_of.rs
Normal file
18
components/layout_2020/tests/size_of.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
/* 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/. */
|
||||
|
||||
#![cfg(target_pointer_width = "64")]
|
||||
|
||||
#[macro_use]
|
||||
extern crate size_of_test;
|
||||
|
||||
use layout::Fragment;
|
||||
use layout::SpecificFragmentInfo;
|
||||
|
||||
size_of_test!(test_size_of_fragment, Fragment, 176);
|
||||
size_of_test!(
|
||||
test_size_of_specific_fragment_info,
|
||||
SpecificFragmentInfo,
|
||||
24
|
||||
);
|
798
components/layout_2020/text.rs
Normal file
798
components/layout_2020/text.rs
Normal file
|
@ -0,0 +1,798 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Text layout.
|
||||
|
||||
use crate::context::LayoutFontContext;
|
||||
use crate::fragment::{Fragment, ScannedTextFlags};
|
||||
use crate::fragment::{ScannedTextFragmentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo};
|
||||
use crate::inline::{InlineFragmentNodeFlags, InlineFragments};
|
||||
use crate::linked_list::split_off_head;
|
||||
use app_units::Au;
|
||||
use gfx::font::{FontMetrics, FontRef, RunMetrics, ShapingFlags, ShapingOptions};
|
||||
use gfx::text::glyph::ByteIndex;
|
||||
use gfx::text::text_run::TextRun;
|
||||
use gfx::text::util::{self, CompressionMode};
|
||||
use range::Range;
|
||||
use servo_atoms::Atom;
|
||||
use std::borrow::ToOwned;
|
||||
use std::collections::LinkedList;
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
use style::computed_values::text_rendering::T as TextRendering;
|
||||
use style::computed_values::white_space::T as WhiteSpace;
|
||||
use style::computed_values::word_break::T as WordBreak;
|
||||
use style::logical_geometry::{LogicalSize, WritingMode};
|
||||
use style::properties::style_structs::Font as FontStyleStruct;
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::generics::text::LineHeight;
|
||||
use style::values::specified::text::{TextTransform, TextTransformCase};
|
||||
use unicode_bidi as bidi;
|
||||
use unicode_script::{get_script, Script};
|
||||
use xi_unicode::LineBreakLeafIter;
|
||||
|
||||
/// Returns the concatenated text of a list of unscanned text fragments.
|
||||
fn text(fragments: &LinkedList<Fragment>) -> String {
|
||||
// FIXME: Some of this work is later duplicated in split_first_fragment_at_newline_if_necessary
|
||||
// and transform_text. This code should be refactored so that the all the scanning for
|
||||
// newlines is done in a single pass.
|
||||
|
||||
let mut text = String::new();
|
||||
|
||||
for fragment in fragments {
|
||||
if let SpecificFragmentInfo::UnscannedText(ref info) = fragment.specific {
|
||||
if fragment.white_space().preserve_newlines() {
|
||||
text.push_str(&info.text);
|
||||
} else {
|
||||
text.push_str(&info.text.replace("\n", " "));
|
||||
}
|
||||
}
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s.
|
||||
pub struct TextRunScanner {
|
||||
pub clump: LinkedList<Fragment>,
|
||||
}
|
||||
|
||||
impl TextRunScanner {
|
||||
pub fn new() -> TextRunScanner {
|
||||
TextRunScanner {
|
||||
clump: LinkedList::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scan_for_runs(
|
||||
&mut self,
|
||||
font_context: &mut LayoutFontContext,
|
||||
mut fragments: LinkedList<Fragment>,
|
||||
) -> InlineFragments {
|
||||
debug!(
|
||||
"TextRunScanner: scanning {} fragments for text runs...",
|
||||
fragments.len()
|
||||
);
|
||||
debug_assert!(!fragments.is_empty());
|
||||
|
||||
// Calculate bidi embedding levels, so we can split bidirectional fragments for reordering.
|
||||
let text = text(&fragments);
|
||||
let para_level = fragments
|
||||
.front()
|
||||
.unwrap()
|
||||
.style
|
||||
.writing_mode
|
||||
.to_bidi_level();
|
||||
let bidi_info = bidi::BidiInfo::new(&text, Some(para_level));
|
||||
|
||||
// Optimization: If all the text is LTR, don't bother splitting on bidi levels.
|
||||
let bidi_levels = if bidi_info.has_rtl() {
|
||||
Some(&bidi_info.levels[..])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// FIXME(pcwalton): We want to be sure not to allocate multiple times, since this is a
|
||||
// performance-critical spot, but this may overestimate and allocate too much memory.
|
||||
let mut new_fragments = Vec::with_capacity(fragments.len());
|
||||
let mut last_whitespace = false;
|
||||
let mut paragraph_bytes_processed = 0;
|
||||
|
||||
// The first time we process a text run we will set this
|
||||
// linebreaker. There is no way for the linebreaker to start
|
||||
// with an empty state; you must give it its first input immediately.
|
||||
//
|
||||
// This linebreaker is shared across text runs, so we can know if
|
||||
// there is a break at the beginning of a text run or clump, e.g.
|
||||
// in the case of FooBar<span>Baz</span>
|
||||
let mut linebreaker = None;
|
||||
|
||||
while !fragments.is_empty() {
|
||||
// Create a clump.
|
||||
split_first_fragment_at_newline_if_necessary(&mut fragments);
|
||||
self.clump.append(&mut split_off_head(&mut fragments));
|
||||
while !fragments.is_empty() &&
|
||||
self.clump
|
||||
.back()
|
||||
.unwrap()
|
||||
.can_merge_with_fragment(fragments.front().unwrap())
|
||||
{
|
||||
split_first_fragment_at_newline_if_necessary(&mut fragments);
|
||||
self.clump.append(&mut split_off_head(&mut fragments));
|
||||
}
|
||||
|
||||
// Flush that clump to the list of fragments we're building up.
|
||||
last_whitespace = self.flush_clump_to_list(
|
||||
font_context,
|
||||
&mut new_fragments,
|
||||
&mut paragraph_bytes_processed,
|
||||
bidi_levels,
|
||||
last_whitespace,
|
||||
&mut linebreaker,
|
||||
);
|
||||
}
|
||||
|
||||
debug!("TextRunScanner: complete.");
|
||||
InlineFragments {
|
||||
fragments: new_fragments,
|
||||
}
|
||||
}
|
||||
|
||||
/// A "clump" is a range of inline flow leaves that can be merged together into a single
|
||||
/// fragment. Adjacent text with the same style can be merged, and nothing else can.
|
||||
///
|
||||
/// The flow keeps track of the fragments contained by all non-leaf DOM nodes. This is necessary
|
||||
/// for correct painting order. Since we compress several leaf fragments here, the mapping must
|
||||
/// be adjusted.
|
||||
fn flush_clump_to_list(
|
||||
&mut self,
|
||||
mut font_context: &mut LayoutFontContext,
|
||||
out_fragments: &mut Vec<Fragment>,
|
||||
paragraph_bytes_processed: &mut usize,
|
||||
bidi_levels: Option<&[bidi::Level]>,
|
||||
mut last_whitespace: bool,
|
||||
linebreaker: &mut Option<LineBreakLeafIter>,
|
||||
) -> bool {
|
||||
debug!(
|
||||
"TextRunScanner: flushing {} fragments in range",
|
||||
self.clump.len()
|
||||
);
|
||||
|
||||
debug_assert!(!self.clump.is_empty());
|
||||
match self.clump.front().unwrap().specific {
|
||||
SpecificFragmentInfo::UnscannedText(_) => {},
|
||||
_ => {
|
||||
debug_assert!(
|
||||
self.clump.len() == 1,
|
||||
"WAT: can't coalesce non-text nodes in flush_clump_to_list()!"
|
||||
);
|
||||
out_fragments.push(self.clump.pop_front().unwrap());
|
||||
return false;
|
||||
},
|
||||
}
|
||||
|
||||
// Concatenate all of the transformed strings together, saving the new character indices.
|
||||
let mut mappings: Vec<RunMapping> = Vec::new();
|
||||
let runs = {
|
||||
let font_group;
|
||||
let compression;
|
||||
let text_transform;
|
||||
let letter_spacing;
|
||||
let word_spacing;
|
||||
let text_rendering;
|
||||
let word_break;
|
||||
{
|
||||
let in_fragment = self.clump.front().unwrap();
|
||||
let font_style = in_fragment.style().clone_font();
|
||||
let inherited_text_style = in_fragment.style().get_inherited_text();
|
||||
font_group = font_context.font_group(font_style);
|
||||
compression = match in_fragment.white_space() {
|
||||
WhiteSpace::Normal | WhiteSpace::Nowrap => {
|
||||
CompressionMode::CompressWhitespaceNewline
|
||||
},
|
||||
WhiteSpace::Pre | WhiteSpace::PreWrap => CompressionMode::CompressNone,
|
||||
WhiteSpace::PreLine => CompressionMode::CompressWhitespace,
|
||||
};
|
||||
text_transform = inherited_text_style.text_transform;
|
||||
letter_spacing = inherited_text_style.letter_spacing;
|
||||
word_spacing = inherited_text_style.word_spacing.to_hash_key();
|
||||
text_rendering = inherited_text_style.text_rendering;
|
||||
word_break = inherited_text_style.word_break;
|
||||
}
|
||||
|
||||
// First, transform/compress text of all the nodes.
|
||||
let (mut run_info_list, mut run_info) = (Vec::new(), RunInfo::new());
|
||||
let mut insertion_point = None;
|
||||
|
||||
for (fragment_index, in_fragment) in self.clump.iter().enumerate() {
|
||||
debug!(" flushing {:?}", in_fragment);
|
||||
let mut mapping = RunMapping::new(&run_info_list[..], fragment_index);
|
||||
let text;
|
||||
let selection;
|
||||
match in_fragment.specific {
|
||||
SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => {
|
||||
text = &text_fragment_info.text;
|
||||
selection = text_fragment_info.selection;
|
||||
},
|
||||
_ => panic!("Expected an unscanned text fragment!"),
|
||||
};
|
||||
insertion_point = match selection {
|
||||
Some(range) if range.is_empty() => {
|
||||
// `range` is the range within the current fragment. To get the range
|
||||
// within the text run, offset it by the length of the preceding fragments.
|
||||
Some(range.begin() + ByteIndex(run_info.text.len() as isize))
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let (mut start_position, mut end_position) = (0, 0);
|
||||
for (byte_index, character) in text.char_indices() {
|
||||
if !character.is_control() {
|
||||
let font = font_group
|
||||
.borrow_mut()
|
||||
.find_by_codepoint(&mut font_context, character);
|
||||
|
||||
let bidi_level = match bidi_levels {
|
||||
Some(levels) => levels[*paragraph_bytes_processed],
|
||||
None => bidi::Level::ltr(),
|
||||
};
|
||||
|
||||
// Break the run if the new character has a different explicit script than the
|
||||
// previous characters.
|
||||
//
|
||||
// TODO: Special handling of paired punctuation characters.
|
||||
// http://www.unicode.org/reports/tr24/#Common
|
||||
let script = get_script(character);
|
||||
let compatible_script = is_compatible(script, run_info.script);
|
||||
if compatible_script && !is_specific(run_info.script) && is_specific(script)
|
||||
{
|
||||
run_info.script = script;
|
||||
}
|
||||
|
||||
let selected = match selection {
|
||||
Some(range) => range.contains(ByteIndex(byte_index as isize)),
|
||||
None => false,
|
||||
};
|
||||
|
||||
// Now, if necessary, flush the mapping we were building up.
|
||||
let flush_run = !run_info.has_font(&font) ||
|
||||
run_info.bidi_level != bidi_level ||
|
||||
!compatible_script;
|
||||
let new_mapping_needed = flush_run || mapping.selected != selected;
|
||||
|
||||
if new_mapping_needed {
|
||||
// We ignore empty mappings at the very start of a fragment.
|
||||
// The run info values are uninitialized at this point so
|
||||
// flushing an empty mapping is pointless.
|
||||
if end_position > 0 {
|
||||
mapping.flush(
|
||||
&mut mappings,
|
||||
&mut run_info,
|
||||
&**text,
|
||||
compression,
|
||||
text_transform,
|
||||
&mut last_whitespace,
|
||||
&mut start_position,
|
||||
end_position,
|
||||
);
|
||||
}
|
||||
if run_info.text.len() > 0 {
|
||||
if flush_run {
|
||||
run_info.flush(&mut run_info_list, &mut insertion_point);
|
||||
run_info = RunInfo::new();
|
||||
}
|
||||
mapping = RunMapping::new(&run_info_list[..], fragment_index);
|
||||
}
|
||||
run_info.font = font;
|
||||
run_info.bidi_level = bidi_level;
|
||||
run_info.script = script;
|
||||
mapping.selected = selected;
|
||||
}
|
||||
}
|
||||
|
||||
// Consume this character.
|
||||
end_position += character.len_utf8();
|
||||
*paragraph_bytes_processed += character.len_utf8();
|
||||
}
|
||||
|
||||
// Flush the last mapping we created for this fragment to the list.
|
||||
mapping.flush(
|
||||
&mut mappings,
|
||||
&mut run_info,
|
||||
&**text,
|
||||
compression,
|
||||
text_transform,
|
||||
&mut last_whitespace,
|
||||
&mut start_position,
|
||||
end_position,
|
||||
);
|
||||
}
|
||||
|
||||
// Push the final run info.
|
||||
run_info.flush(&mut run_info_list, &mut insertion_point);
|
||||
|
||||
// Per CSS 2.1 § 16.4, "when the resultant space between two characters is not the same
|
||||
// as the default space, user agents should not use ligatures." This ensures that, for
|
||||
// example, `finally` with a wide `letter-spacing` renders as `f i n a l l y` and not
|
||||
// `fi n a l l y`.
|
||||
let mut flags = ShapingFlags::empty();
|
||||
if letter_spacing.0.px() != 0. {
|
||||
flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
|
||||
}
|
||||
if text_rendering == TextRendering::Optimizespeed {
|
||||
flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
|
||||
flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
|
||||
}
|
||||
if word_break == WordBreak::KeepAll {
|
||||
flags.insert(ShapingFlags::KEEP_ALL_FLAG);
|
||||
}
|
||||
let options = ShapingOptions {
|
||||
letter_spacing: if letter_spacing.0.px() == 0. {
|
||||
None
|
||||
} else {
|
||||
Some(Au::from(letter_spacing.0))
|
||||
},
|
||||
word_spacing,
|
||||
script: Script::Common,
|
||||
flags: flags,
|
||||
};
|
||||
|
||||
let mut result = Vec::with_capacity(run_info_list.len());
|
||||
for run_info in run_info_list {
|
||||
let mut options = options;
|
||||
options.script = run_info.script;
|
||||
if run_info.bidi_level.is_rtl() {
|
||||
options.flags.insert(ShapingFlags::RTL_FLAG);
|
||||
}
|
||||
|
||||
// If no font is found (including fallbacks), there's no way we can render.
|
||||
let font = run_info
|
||||
.font
|
||||
.or_else(|| font_group.borrow_mut().first(&mut font_context))
|
||||
.expect("No font found for text run!");
|
||||
|
||||
let (run, break_at_zero) = TextRun::new(
|
||||
&mut *font.borrow_mut(),
|
||||
run_info.text,
|
||||
&options,
|
||||
run_info.bidi_level,
|
||||
linebreaker,
|
||||
);
|
||||
result.push((
|
||||
ScannedTextRun {
|
||||
run: Arc::new(run),
|
||||
insertion_point: run_info.insertion_point,
|
||||
},
|
||||
break_at_zero,
|
||||
))
|
||||
}
|
||||
result
|
||||
};
|
||||
|
||||
// Make new fragments with the runs and adjusted text indices.
|
||||
debug!("TextRunScanner: pushing {} fragment(s)", self.clump.len());
|
||||
let mut mappings = mappings.into_iter().peekable();
|
||||
let mut prev_fragments_to_meld = Vec::new();
|
||||
|
||||
for (logical_offset, old_fragment) in mem::replace(&mut self.clump, LinkedList::new())
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let mut is_first_mapping_of_this_old_fragment = true;
|
||||
loop {
|
||||
match mappings.peek() {
|
||||
Some(mapping) if mapping.old_fragment_index == logical_offset => {},
|
||||
Some(_) | None => {
|
||||
if is_first_mapping_of_this_old_fragment {
|
||||
// There were no mappings for this unscanned fragment. Transfer its
|
||||
// flags to the previous/next sibling elements instead.
|
||||
if let Some(ref mut last_fragment) = out_fragments.last_mut() {
|
||||
last_fragment.meld_with_next_inline_fragment(&old_fragment);
|
||||
}
|
||||
prev_fragments_to_meld.push(old_fragment);
|
||||
}
|
||||
break;
|
||||
},
|
||||
};
|
||||
let mapping = mappings.next().unwrap();
|
||||
let (scanned_run, break_at_zero) = runs[mapping.text_run_index].clone();
|
||||
|
||||
let mut byte_range = Range::new(
|
||||
ByteIndex(mapping.byte_range.begin() as isize),
|
||||
ByteIndex(mapping.byte_range.length() as isize),
|
||||
);
|
||||
|
||||
let mut flags = ScannedTextFlags::empty();
|
||||
if !break_at_zero && mapping.byte_range.begin() == 0 {
|
||||
// If this is the first segment of the text run,
|
||||
// and the text run doesn't break at zero, suppress line breaks
|
||||
flags.insert(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE)
|
||||
}
|
||||
let text_size = old_fragment.border_box.size;
|
||||
|
||||
let requires_line_break_afterward_if_wrapping_on_newlines = scanned_run.run.text
|
||||
[mapping.byte_range.begin()..mapping.byte_range.end()]
|
||||
.ends_with('\n');
|
||||
|
||||
if requires_line_break_afterward_if_wrapping_on_newlines {
|
||||
byte_range.extend_by(ByteIndex(-1)); // Trim the '\n'
|
||||
flags.insert(
|
||||
ScannedTextFlags::REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES,
|
||||
);
|
||||
}
|
||||
|
||||
if mapping.selected {
|
||||
flags.insert(ScannedTextFlags::SELECTED);
|
||||
}
|
||||
|
||||
let insertion_point =
|
||||
if mapping.contains_insertion_point(scanned_run.insertion_point) {
|
||||
scanned_run.insertion_point
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut new_text_fragment_info = Box::new(ScannedTextFragmentInfo::new(
|
||||
scanned_run.run,
|
||||
byte_range,
|
||||
text_size,
|
||||
insertion_point,
|
||||
flags,
|
||||
));
|
||||
|
||||
let new_metrics = new_text_fragment_info.run.metrics_for_range(&byte_range);
|
||||
let writing_mode = old_fragment.style.writing_mode;
|
||||
let bounding_box_size = bounding_box_for_run_metrics(&new_metrics, writing_mode);
|
||||
new_text_fragment_info.content_size = bounding_box_size;
|
||||
|
||||
let mut new_fragment = old_fragment.transform(
|
||||
bounding_box_size,
|
||||
SpecificFragmentInfo::ScannedText(new_text_fragment_info),
|
||||
);
|
||||
|
||||
let is_last_mapping_of_this_old_fragment = match mappings.peek() {
|
||||
Some(mapping) if mapping.old_fragment_index == logical_offset => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if let Some(ref mut context) = new_fragment.inline_context {
|
||||
for node in &mut context.nodes {
|
||||
if !is_last_mapping_of_this_old_fragment {
|
||||
node.flags
|
||||
.remove(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT);
|
||||
}
|
||||
if !is_first_mapping_of_this_old_fragment {
|
||||
node.flags
|
||||
.remove(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for prev_fragment in prev_fragments_to_meld.drain(..) {
|
||||
new_fragment.meld_with_prev_inline_fragment(&prev_fragment);
|
||||
}
|
||||
|
||||
is_first_mapping_of_this_old_fragment = false;
|
||||
out_fragments.push(new_fragment)
|
||||
}
|
||||
}
|
||||
|
||||
last_whitespace
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bounding_box_for_run_metrics(
|
||||
metrics: &RunMetrics,
|
||||
writing_mode: WritingMode,
|
||||
) -> LogicalSize<Au> {
|
||||
// TODO: When the text-orientation property is supported, the block and inline directions may
|
||||
// be swapped for horizontal glyphs in vertical lines.
|
||||
LogicalSize::new(
|
||||
writing_mode,
|
||||
metrics.bounding_box.size.width,
|
||||
metrics.bounding_box.size.height,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the metrics of the font represented by the given `FontStyleStruct`.
|
||||
///
|
||||
/// `#[inline]` because often the caller only needs a few fields from the font metrics.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no font can be found for the given font style.
|
||||
#[inline]
|
||||
pub fn font_metrics_for_style(
|
||||
mut font_context: &mut LayoutFontContext,
|
||||
style: crate::ServoArc<FontStyleStruct>,
|
||||
) -> FontMetrics {
|
||||
let font_group = font_context.font_group(style);
|
||||
let font = font_group.borrow_mut().first(&mut font_context);
|
||||
let font = font.as_ref().unwrap().borrow();
|
||||
|
||||
font.metrics.clone()
|
||||
}
|
||||
|
||||
/// Returns the line block-size needed by the given computed style and font size.
|
||||
pub fn line_height_from_style(style: &ComputedValues, metrics: &FontMetrics) -> Au {
|
||||
let font_size = style.get_font().font_size.size();
|
||||
match style.get_inherited_text().line_height {
|
||||
LineHeight::Normal => Au::from(metrics.line_gap),
|
||||
LineHeight::Number(l) => font_size.scale_by(l.0),
|
||||
LineHeight::Length(l) => Au::from(l),
|
||||
}
|
||||
}
|
||||
|
||||
fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragment>) {
|
||||
if fragments.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_fragment = {
|
||||
let first_fragment = fragments.front_mut().unwrap();
|
||||
let string_before;
|
||||
let selection_before;
|
||||
{
|
||||
if !first_fragment.white_space().preserve_newlines() {
|
||||
return;
|
||||
}
|
||||
|
||||
let unscanned_text_fragment_info = match first_fragment.specific {
|
||||
SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => {
|
||||
unscanned_text_fragment_info
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let position = match unscanned_text_fragment_info.text.find('\n') {
|
||||
Some(position) if position < unscanned_text_fragment_info.text.len() - 1 => {
|
||||
position
|
||||
},
|
||||
Some(_) | None => return,
|
||||
};
|
||||
|
||||
string_before = unscanned_text_fragment_info.text[..(position + 1)].to_owned();
|
||||
unscanned_text_fragment_info.text = unscanned_text_fragment_info.text[(position + 1)..]
|
||||
.to_owned()
|
||||
.into_boxed_str();
|
||||
let offset = ByteIndex(string_before.len() as isize);
|
||||
match unscanned_text_fragment_info.selection {
|
||||
Some(ref mut selection) if selection.begin() >= offset => {
|
||||
// Selection is entirely in the second fragment.
|
||||
selection_before = None;
|
||||
selection.shift_by(-offset);
|
||||
},
|
||||
Some(ref mut selection) if selection.end() > offset => {
|
||||
// Selection is split across two fragments.
|
||||
selection_before = Some(Range::new(selection.begin(), offset));
|
||||
*selection = Range::new(ByteIndex(0), selection.end() - offset);
|
||||
},
|
||||
_ => {
|
||||
// Selection is entirely in the first fragment.
|
||||
selection_before = unscanned_text_fragment_info.selection;
|
||||
unscanned_text_fragment_info.selection = None;
|
||||
},
|
||||
};
|
||||
}
|
||||
first_fragment.transform(
|
||||
first_fragment.border_box.size,
|
||||
SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new(
|
||||
string_before.into_boxed_str(),
|
||||
selection_before,
|
||||
))),
|
||||
)
|
||||
};
|
||||
|
||||
fragments.push_front(new_fragment);
|
||||
}
|
||||
|
||||
/// Information about a text run that we're about to create. This is used in `scan_for_runs`.
|
||||
struct RunInfo {
|
||||
/// The text that will go in this text run.
|
||||
text: String,
|
||||
/// The insertion point in this text run, if applicable.
|
||||
insertion_point: Option<ByteIndex>,
|
||||
/// The font that the text should be rendered with.
|
||||
font: Option<FontRef>,
|
||||
/// The bidirection embedding level of this text run.
|
||||
bidi_level: bidi::Level,
|
||||
/// The Unicode script property of this text run.
|
||||
script: Script,
|
||||
}
|
||||
|
||||
impl RunInfo {
|
||||
fn new() -> RunInfo {
|
||||
RunInfo {
|
||||
text: String::new(),
|
||||
insertion_point: None,
|
||||
font: None,
|
||||
bidi_level: bidi::Level::ltr(),
|
||||
script: Script::Common,
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish processing this RunInfo and add it to the "done" list.
|
||||
///
|
||||
/// * `insertion_point`: The position of the insertion point, in characters relative to the start
|
||||
/// of this text run.
|
||||
fn flush(mut self, list: &mut Vec<RunInfo>, insertion_point: &mut Option<ByteIndex>) {
|
||||
if let Some(idx) = *insertion_point {
|
||||
let char_len = ByteIndex(self.text.len() as isize);
|
||||
if idx <= char_len {
|
||||
// The insertion point is in this text run.
|
||||
self.insertion_point = insertion_point.take()
|
||||
} else {
|
||||
// Continue looking for the insertion point in the next text run.
|
||||
*insertion_point = Some(idx - char_len)
|
||||
}
|
||||
}
|
||||
list.push(self);
|
||||
}
|
||||
|
||||
fn has_font(&self, font: &Option<FontRef>) -> bool {
|
||||
fn identifier(font: &Option<FontRef>) -> Option<Atom> {
|
||||
font.as_ref().map(|f| f.borrow().identifier())
|
||||
}
|
||||
|
||||
identifier(&self.font) == identifier(font)
|
||||
}
|
||||
}
|
||||
|
||||
/// A mapping from a portion of an unscanned text fragment to the text run we're going to create
|
||||
/// for it.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct RunMapping {
|
||||
/// The range of byte indices within the text fragment.
|
||||
byte_range: Range<usize>,
|
||||
/// The index of the unscanned text fragment that this mapping corresponds to.
|
||||
old_fragment_index: usize,
|
||||
/// The index of the text run we're going to create.
|
||||
text_run_index: usize,
|
||||
/// Is the text in this fragment selected?
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl RunMapping {
|
||||
/// Given the current set of text runs, creates a run mapping for the next fragment.
|
||||
/// `run_info_list` describes the set of runs we've seen already.
|
||||
fn new(run_info_list: &[RunInfo], fragment_index: usize) -> RunMapping {
|
||||
RunMapping {
|
||||
byte_range: Range::new(0, 0),
|
||||
old_fragment_index: fragment_index,
|
||||
text_run_index: run_info_list.len(),
|
||||
selected: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Flushes this run mapping to the list. `run_info` describes the text run that we're
|
||||
/// currently working on. `text` refers to the text of this fragment.
|
||||
fn flush(
|
||||
mut self,
|
||||
mappings: &mut Vec<RunMapping>,
|
||||
run_info: &mut RunInfo,
|
||||
text: &str,
|
||||
compression: CompressionMode,
|
||||
text_transform: TextTransform,
|
||||
last_whitespace: &mut bool,
|
||||
start_position: &mut usize,
|
||||
end_position: usize,
|
||||
) {
|
||||
let was_empty = *start_position == end_position;
|
||||
let old_byte_length = run_info.text.len();
|
||||
*last_whitespace = util::transform_text(
|
||||
&text[(*start_position)..end_position],
|
||||
compression,
|
||||
*last_whitespace,
|
||||
&mut run_info.text,
|
||||
);
|
||||
|
||||
// Account for `text-transform`. (Confusingly, this is not handled in "text
|
||||
// transformation" above, but we follow Gecko in the naming.)
|
||||
let is_first_run = *start_position == 0;
|
||||
apply_style_transform_if_necessary(
|
||||
&mut run_info.text,
|
||||
old_byte_length,
|
||||
text_transform,
|
||||
*last_whitespace,
|
||||
is_first_run,
|
||||
);
|
||||
*start_position = end_position;
|
||||
|
||||
let new_byte_length = run_info.text.len();
|
||||
let is_empty = new_byte_length == old_byte_length;
|
||||
|
||||
// Don't save mappings that contain only discarded characters.
|
||||
// (But keep ones that contained no characters to begin with, since they might have been
|
||||
// generated by an empty flow to draw its borders/padding/insertion point.)
|
||||
if is_empty && !was_empty {
|
||||
return;
|
||||
}
|
||||
|
||||
self.byte_range = Range::new(old_byte_length, new_byte_length - old_byte_length);
|
||||
mappings.push(self)
|
||||
}
|
||||
|
||||
/// Is the insertion point for this text run within this mapping?
|
||||
///
|
||||
/// NOTE: We treat the range as inclusive at both ends, since the insertion point can lie
|
||||
/// before the first character *or* after the last character, and should be drawn even if the
|
||||
/// text is empty.
|
||||
fn contains_insertion_point(&self, insertion_point: Option<ByteIndex>) -> bool {
|
||||
match insertion_point.map(ByteIndex::to_usize) {
|
||||
None => false,
|
||||
Some(idx) => self.byte_range.begin() <= idx && idx <= self.byte_range.end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Accounts for `text-transform`.
|
||||
///
|
||||
/// FIXME(#4311, pcwalton): Title-case mapping can change length of the string;
|
||||
/// case mapping should be language-specific; `full-width`;
|
||||
/// use graphemes instead of characters.
|
||||
fn apply_style_transform_if_necessary(
|
||||
string: &mut String,
|
||||
first_character_position: usize,
|
||||
text_transform: TextTransform,
|
||||
last_whitespace: bool,
|
||||
is_first_run: bool,
|
||||
) {
|
||||
match text_transform.case_ {
|
||||
TextTransformCase::None => {},
|
||||
TextTransformCase::Uppercase => {
|
||||
let original = string[first_character_position..].to_owned();
|
||||
string.truncate(first_character_position);
|
||||
for ch in original.chars().flat_map(|ch| ch.to_uppercase()) {
|
||||
string.push(ch);
|
||||
}
|
||||
},
|
||||
TextTransformCase::Lowercase => {
|
||||
let original = string[first_character_position..].to_owned();
|
||||
string.truncate(first_character_position);
|
||||
for ch in original.chars().flat_map(|ch| ch.to_lowercase()) {
|
||||
string.push(ch);
|
||||
}
|
||||
},
|
||||
TextTransformCase::Capitalize => {
|
||||
let original = string[first_character_position..].to_owned();
|
||||
string.truncate(first_character_position);
|
||||
|
||||
let mut capitalize_next_letter = is_first_run || last_whitespace;
|
||||
for character in original.chars() {
|
||||
// FIXME(#4311, pcwalton): Should be the CSS/Unicode notion of a *typographic
|
||||
// letter unit*, not an *alphabetic* character:
|
||||
//
|
||||
// http://dev.w3.org/csswg/css-text/#typographic-letter-unit
|
||||
if capitalize_next_letter && character.is_alphabetic() {
|
||||
string.push(character.to_uppercase().next().unwrap());
|
||||
capitalize_next_letter = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
string.push(character);
|
||||
|
||||
// FIXME(#4311, pcwalton): Try UAX29 instead of just whitespace.
|
||||
if character.is_whitespace() {
|
||||
capitalize_next_letter = true
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ScannedTextRun {
|
||||
run: Arc<TextRun>,
|
||||
insertion_point: Option<ByteIndex>,
|
||||
}
|
||||
|
||||
/// Can a character with script `b` continue a text run with script `a`?
|
||||
fn is_compatible(a: Script, b: Script) -> bool {
|
||||
a == b || !is_specific(a) || !is_specific(b)
|
||||
}
|
||||
|
||||
/// Returns true if the script is not invalid or inherited.
|
||||
fn is_specific(script: Script) -> bool {
|
||||
script != Script::Common && script != Script::Inherited
|
||||
}
|
350
components/layout_2020/traversal.rs
Normal file
350
components/layout_2020/traversal.rs
Normal file
|
@ -0,0 +1,350 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Traversals over the DOM and flow trees, running the layout computations.
|
||||
|
||||
use crate::construct::FlowConstructor;
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::DisplayListBuildState;
|
||||
use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
|
||||
use crate::wrapper::ThreadSafeLayoutNodeHelpers;
|
||||
use crate::wrapper::{GetRawData, LayoutNodeLayoutData};
|
||||
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
|
||||
use servo_config::opts;
|
||||
use style::context::{SharedStyleContext, StyleContext};
|
||||
use style::data::ElementData;
|
||||
use style::dom::{NodeInfo, TElement, TNode};
|
||||
use style::selector_parser::RestyleDamage;
|
||||
use style::servo::restyle_damage::ServoRestyleDamage;
|
||||
use style::traversal::PerLevelTraversalData;
|
||||
use style::traversal::{recalc_style_at, DomTraversal};
|
||||
|
||||
pub struct RecalcStyleAndConstructFlows<'a> {
|
||||
context: LayoutContext<'a>,
|
||||
}
|
||||
|
||||
impl<'a> RecalcStyleAndConstructFlows<'a> {
|
||||
/// Creates a traversal context, taking ownership of the shared layout context.
|
||||
pub fn new(context: LayoutContext<'a>) -> Self {
|
||||
RecalcStyleAndConstructFlows { context: context }
|
||||
}
|
||||
|
||||
/// Consumes this traversal context, returning ownership of the shared layout
|
||||
/// context to the caller.
|
||||
pub fn destroy(self) -> LayoutContext<'a> {
|
||||
self.context
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
impl<'a, E> DomTraversal<E> for RecalcStyleAndConstructFlows<'a>
|
||||
where
|
||||
E: TElement,
|
||||
E::ConcreteNode: LayoutNode,
|
||||
E::FontMetricsProvider: Send,
|
||||
{
|
||||
fn process_preorder<F>(
|
||||
&self,
|
||||
traversal_data: &PerLevelTraversalData,
|
||||
context: &mut StyleContext<E>,
|
||||
node: E::ConcreteNode,
|
||||
note_child: F,
|
||||
) where
|
||||
F: FnMut(E::ConcreteNode),
|
||||
{
|
||||
// FIXME(pcwalton): Stop allocating here. Ideally this should just be
|
||||
// done by the HTML parser.
|
||||
unsafe { node.initialize_data() };
|
||||
|
||||
if !node.is_text_node() {
|
||||
let el = node.as_element().unwrap();
|
||||
let mut data = el.mutate_data().unwrap();
|
||||
recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
|
||||
}
|
||||
}
|
||||
|
||||
fn process_postorder(&self, _style_context: &mut StyleContext<E>, node: E::ConcreteNode) {
|
||||
construct_flows_at(&self.context, node);
|
||||
}
|
||||
|
||||
fn text_node_needs_traversal(node: E::ConcreteNode, parent_data: &ElementData) -> bool {
|
||||
// Text nodes never need styling. However, there are two cases they may need
|
||||
// flow construction:
|
||||
// (1) They child doesn't yet have layout data (preorder traversal initializes it).
|
||||
// (2) The parent element has restyle damage (so the text flow also needs fixup).
|
||||
node.get_raw_data().is_none() || !parent_data.damage.is_empty()
|
||||
}
|
||||
|
||||
fn shared_context(&self) -> &SharedStyleContext {
|
||||
&self.context.style_context
|
||||
}
|
||||
}
|
||||
|
||||
/// A top-down traversal.
|
||||
pub trait PreorderFlowTraversal {
|
||||
/// The operation to perform. Return true to continue or false to stop.
|
||||
fn process(&self, flow: &mut dyn Flow);
|
||||
|
||||
/// Returns true if this node should be processed and false if neither this node nor its
|
||||
/// descendants should be processed.
|
||||
fn should_process_subtree(&self, _flow: &mut dyn Flow) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns true if this node must be processed in-order. If this returns false,
|
||||
/// we skip the operation for this node, but continue processing the descendants.
|
||||
/// This is called *after* parent nodes are visited.
|
||||
fn should_process(&self, _flow: &mut dyn Flow) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Traverses the tree in preorder.
|
||||
fn traverse(&self, flow: &mut dyn Flow) {
|
||||
if !self.should_process_subtree(flow) {
|
||||
return;
|
||||
}
|
||||
if self.should_process(flow) {
|
||||
self.process(flow);
|
||||
}
|
||||
for kid in flow.mut_base().child_iter_mut() {
|
||||
self.traverse(kid);
|
||||
}
|
||||
}
|
||||
|
||||
/// Traverse the Absolute flow tree in preorder.
|
||||
///
|
||||
/// Traverse all your direct absolute descendants, who will then traverse
|
||||
/// their direct absolute descendants.
|
||||
///
|
||||
/// Return true if the traversal is to continue or false to stop.
|
||||
fn traverse_absolute_flows(&self, flow: &mut dyn Flow) {
|
||||
if self.should_process(flow) {
|
||||
self.process(flow);
|
||||
}
|
||||
for descendant_link in flow.mut_base().abs_descendants.iter() {
|
||||
self.traverse_absolute_flows(descendant_link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A bottom-up traversal, with a optional in-order pass.
|
||||
pub trait PostorderFlowTraversal {
|
||||
/// The operation to perform. Return true to continue or false to stop.
|
||||
fn process(&self, flow: &mut dyn Flow);
|
||||
|
||||
/// Returns false if this node must be processed in-order. If this returns false, we skip the
|
||||
/// operation for this node, but continue processing the ancestors. This is called *after*
|
||||
/// child nodes are visited.
|
||||
fn should_process(&self, _flow: &mut dyn Flow) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Traverses the tree in postorder.
|
||||
fn traverse(&self, flow: &mut dyn Flow) {
|
||||
for kid in flow.mut_base().child_iter_mut() {
|
||||
self.traverse(kid);
|
||||
}
|
||||
if self.should_process(flow) {
|
||||
self.process(flow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An in-order (sequential only) traversal.
|
||||
pub trait InorderFlowTraversal {
|
||||
/// The operation to perform. Returns the level of the tree we're at.
|
||||
fn process(&mut self, flow: &mut dyn Flow, level: u32);
|
||||
|
||||
/// Returns true if this node should be processed and false if neither this node nor its
|
||||
/// descendants should be processed.
|
||||
fn should_process_subtree(&mut self, _flow: &mut dyn Flow) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Traverses the tree in-order.
|
||||
fn traverse(&mut self, flow: &mut dyn Flow, level: u32) {
|
||||
if !self.should_process_subtree(flow) {
|
||||
return;
|
||||
}
|
||||
self.process(flow, level);
|
||||
for kid in flow.mut_base().child_iter_mut() {
|
||||
self.traverse(kid, level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A bottom-up, parallelizable traversal.
|
||||
pub trait PostorderNodeMutTraversal<ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> {
|
||||
/// The operation to perform. Return true to continue or false to stop.
|
||||
fn process(&mut self, node: &ConcreteThreadSafeLayoutNode);
|
||||
}
|
||||
|
||||
/// The flow construction traversal, which builds flows for styled nodes.
|
||||
#[inline]
|
||||
#[allow(unsafe_code)]
|
||||
fn construct_flows_at<N>(context: &LayoutContext, node: N)
|
||||
where
|
||||
N: LayoutNode,
|
||||
{
|
||||
debug!("construct_flows_at: {:?}", node);
|
||||
|
||||
// Construct flows for this node.
|
||||
{
|
||||
let tnode = node.to_threadsafe();
|
||||
|
||||
// Always reconstruct if incremental layout is turned off.
|
||||
let nonincremental_layout = opts::get().nonincremental_layout;
|
||||
if nonincremental_layout ||
|
||||
tnode.restyle_damage() != RestyleDamage::empty() ||
|
||||
node.as_element()
|
||||
.map_or(false, |el| el.has_dirty_descendants())
|
||||
{
|
||||
let mut flow_constructor = FlowConstructor::new(context);
|
||||
if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) {
|
||||
flow_constructor.process(&tnode);
|
||||
debug!(
|
||||
"Constructed flow for {:?}: {:x}",
|
||||
tnode,
|
||||
tnode.flow_debug_id()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
tnode
|
||||
.mutate_layout_data()
|
||||
.unwrap()
|
||||
.flags
|
||||
.insert(crate::data::LayoutDataFlags::HAS_BEEN_TRAVERSED);
|
||||
}
|
||||
|
||||
if let Some(el) = node.as_element() {
|
||||
unsafe {
|
||||
el.unset_dirty_descendants();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The bubble-inline-sizes traversal, the first part of layout computation. This computes
|
||||
/// preferred and intrinsic inline-sizes and bubbles them up the tree.
|
||||
pub struct BubbleISizes<'a> {
|
||||
pub layout_context: &'a LayoutContext<'a>,
|
||||
}
|
||||
|
||||
impl<'a> PostorderFlowTraversal for BubbleISizes<'a> {
|
||||
#[inline]
|
||||
fn process(&self, flow: &mut dyn Flow) {
|
||||
flow.bubble_inline_sizes();
|
||||
flow.mut_base()
|
||||
.restyle_damage
|
||||
.remove(ServoRestyleDamage::BUBBLE_ISIZES);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn should_process(&self, flow: &mut dyn Flow) -> bool {
|
||||
flow.base()
|
||||
.restyle_damage
|
||||
.contains(ServoRestyleDamage::BUBBLE_ISIZES)
|
||||
}
|
||||
}
|
||||
|
||||
/// The assign-inline-sizes traversal. In Gecko this corresponds to `Reflow`.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AssignISizes<'a> {
|
||||
pub layout_context: &'a LayoutContext<'a>,
|
||||
}
|
||||
|
||||
impl<'a> PreorderFlowTraversal for AssignISizes<'a> {
|
||||
#[inline]
|
||||
fn process(&self, flow: &mut dyn Flow) {
|
||||
flow.assign_inline_sizes(self.layout_context);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn should_process(&self, flow: &mut dyn Flow) -> bool {
|
||||
flow.base()
|
||||
.restyle_damage
|
||||
.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW)
|
||||
}
|
||||
}
|
||||
|
||||
/// The assign-block-sizes-and-store-overflow traversal, the last (and most expensive) part of
|
||||
/// layout computation. Determines the final block-sizes for all layout objects and computes
|
||||
/// positions. In Gecko this corresponds to `Reflow`.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AssignBSizes<'a> {
|
||||
pub layout_context: &'a LayoutContext<'a>,
|
||||
}
|
||||
|
||||
impl<'a> PostorderFlowTraversal for AssignBSizes<'a> {
|
||||
#[inline]
|
||||
fn process(&self, flow: &mut dyn Flow) {
|
||||
// Can't do anything with anything that floats might flow through until we reach their
|
||||
// inorder parent.
|
||||
//
|
||||
// NB: We must return without resetting the restyle bits for these, as we haven't actually
|
||||
// reflowed anything!
|
||||
if flow.floats_might_flow_through() {
|
||||
return;
|
||||
}
|
||||
|
||||
flow.assign_block_size(self.layout_context);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn should_process(&self, flow: &mut dyn Flow) -> bool {
|
||||
let base = flow.base();
|
||||
base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) &&
|
||||
// The fragmentation countainer is responsible for calling
|
||||
// Flow::fragment recursively
|
||||
!base.flags.contains(FlowFlags::CAN_BE_FRAGMENTED)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ComputeStackingRelativePositions<'a> {
|
||||
pub layout_context: &'a LayoutContext<'a>,
|
||||
}
|
||||
|
||||
impl<'a> PreorderFlowTraversal for ComputeStackingRelativePositions<'a> {
|
||||
#[inline]
|
||||
fn should_process_subtree(&self, flow: &mut dyn Flow) -> bool {
|
||||
flow.base()
|
||||
.restyle_damage
|
||||
.contains(ServoRestyleDamage::REPOSITION)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn process(&self, flow: &mut dyn Flow) {
|
||||
flow.compute_stacking_relative_position(self.layout_context);
|
||||
flow.mut_base()
|
||||
.restyle_damage
|
||||
.remove(ServoRestyleDamage::REPOSITION)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BuildDisplayList<'a> {
|
||||
pub state: DisplayListBuildState<'a>,
|
||||
}
|
||||
|
||||
impl<'a> BuildDisplayList<'a> {
|
||||
#[inline]
|
||||
pub fn traverse(&mut self, flow: &mut dyn Flow) {
|
||||
let parent_stacking_context_id = self.state.current_stacking_context_id;
|
||||
self.state.current_stacking_context_id = flow.base().stacking_context_id;
|
||||
|
||||
let parent_clipping_and_scrolling = self.state.current_clipping_and_scrolling;
|
||||
self.state.current_clipping_and_scrolling = flow.clipping_and_scrolling();
|
||||
|
||||
flow.build_display_list(&mut self.state);
|
||||
flow.mut_base()
|
||||
.restyle_damage
|
||||
.remove(ServoRestyleDamage::REPAINT);
|
||||
|
||||
for kid in flow.mut_base().child_iter_mut() {
|
||||
self.traverse(kid);
|
||||
}
|
||||
|
||||
self.state.current_stacking_context_id = parent_stacking_context_id;
|
||||
self.state.current_clipping_and_scrolling = parent_clipping_and_scrolling;
|
||||
}
|
||||
}
|
174
components/layout_2020/wrapper.rs
Normal file
174
components/layout_2020/wrapper.rs
Normal file
|
@ -0,0 +1,174 @@
|
|||
/* 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/. */
|
||||
|
||||
//! A safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes
|
||||
//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via
|
||||
//! a simple whitelist of allowed operations, along with some lifetime magic to prevent nodes from
|
||||
//! escaping.
|
||||
//!
|
||||
//! As a security wrapper is only as good as its whitelist, be careful when adding operations to
|
||||
//! this list. The cardinal rules are:
|
||||
//!
|
||||
//! 1. Layout is not allowed to mutate the DOM.
|
||||
//!
|
||||
//! 2. Layout is not allowed to see anything with `LayoutDom` in the name, because it could hang
|
||||
//! onto these objects and cause use-after-free.
|
||||
//!
|
||||
//! When implementing wrapper functions, be careful that you do not touch the borrow flags, or you
|
||||
//! will race and cause spurious thread failure. (Note that I do not believe these races are
|
||||
//! exploitable, but they'll result in brokenness nonetheless.)
|
||||
//!
|
||||
//! Rules of the road for this file:
|
||||
//!
|
||||
//! * Do not call any methods on DOM nodes without checking to see whether they use borrow flags.
|
||||
//!
|
||||
//! o Instead of `get_attr()`, use `.get_attr_val_for_layout()`.
|
||||
//!
|
||||
//! o Instead of `html_element_in_html_document()`, use
|
||||
//! `html_element_in_html_document_for_layout()`.
|
||||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use crate::data::{LayoutData, LayoutDataFlags, StyleAndLayoutData};
|
||||
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
||||
use script_layout_interface::wrapper_traits::GetLayoutData;
|
||||
use script_layout_interface::wrapper_traits::{ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||||
use style::dom::{NodeInfo, TNode};
|
||||
use style::selector_parser::RestyleDamage;
|
||||
use style::values::computed::counters::ContentItem;
|
||||
use style::values::generics::counters::Content;
|
||||
|
||||
pub trait LayoutNodeLayoutData {
|
||||
/// Similar to borrow_data*, but returns the full PersistentLayoutData rather
|
||||
/// than only the style::data::ElementData.
|
||||
fn borrow_layout_data(&self) -> Option<AtomicRef<LayoutData>>;
|
||||
fn mutate_layout_data(&self) -> Option<AtomicRefMut<LayoutData>>;
|
||||
fn flow_debug_id(self) -> usize;
|
||||
}
|
||||
|
||||
impl<T: GetLayoutData> LayoutNodeLayoutData for T {
|
||||
fn borrow_layout_data(&self) -> Option<AtomicRef<LayoutData>> {
|
||||
self.get_raw_data().map(|d| d.layout_data.borrow())
|
||||
}
|
||||
|
||||
fn mutate_layout_data(&self) -> Option<AtomicRefMut<LayoutData>> {
|
||||
self.get_raw_data().map(|d| d.layout_data.borrow_mut())
|
||||
}
|
||||
|
||||
fn flow_debug_id(self) -> usize {
|
||||
self.borrow_layout_data()
|
||||
.map_or(0, |d| d.flow_construction_result.debug_id())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GetRawData {
|
||||
fn get_raw_data(&self) -> Option<&StyleAndLayoutData>;
|
||||
}
|
||||
|
||||
impl<T: GetLayoutData> GetRawData for T {
|
||||
fn get_raw_data(&self) -> Option<&StyleAndLayoutData> {
|
||||
self.get_style_and_layout_data().map(|opaque| {
|
||||
let container = opaque.ptr.as_ptr() as *mut StyleAndLayoutData;
|
||||
unsafe { &*container }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ThreadSafeLayoutNodeHelpers {
|
||||
/// Returns the layout data flags for this node.
|
||||
fn flags(self) -> LayoutDataFlags;
|
||||
|
||||
/// Adds the given flags to this node.
|
||||
fn insert_flags(self, new_flags: LayoutDataFlags);
|
||||
|
||||
/// Removes the given flags from this node.
|
||||
fn remove_flags(self, flags: LayoutDataFlags);
|
||||
|
||||
/// If this is a text node, generated content, or a form element, copies out
|
||||
/// its content. Otherwise, panics.
|
||||
///
|
||||
/// FIXME(pcwalton): This might have too much copying and/or allocation. Profile this.
|
||||
fn text_content(&self) -> TextContent;
|
||||
|
||||
/// The RestyleDamage from any restyling, or RestyleDamage::rebuild_and_reflow() if this
|
||||
/// is the first time layout is visiting this node. We implement this here, rather than
|
||||
/// with the rest of the wrapper layer, because we need layout code to determine whether
|
||||
/// layout has visited the node.
|
||||
fn restyle_damage(self) -> RestyleDamage;
|
||||
}
|
||||
|
||||
impl<T: ThreadSafeLayoutNode> ThreadSafeLayoutNodeHelpers for T {
|
||||
fn flags(self) -> LayoutDataFlags {
|
||||
self.borrow_layout_data().as_ref().unwrap().flags
|
||||
}
|
||||
|
||||
fn insert_flags(self, new_flags: LayoutDataFlags) {
|
||||
self.mutate_layout_data().unwrap().flags.insert(new_flags);
|
||||
}
|
||||
|
||||
fn remove_flags(self, flags: LayoutDataFlags) {
|
||||
self.mutate_layout_data().unwrap().flags.remove(flags);
|
||||
}
|
||||
|
||||
fn text_content(&self) -> TextContent {
|
||||
if self.get_pseudo_element_type().is_replaced_content() {
|
||||
let style = self.as_element().unwrap().resolved_style();
|
||||
|
||||
return TextContent::GeneratedContent(match style.as_ref().get_counters().content {
|
||||
Content::Items(ref value) => value.to_vec(),
|
||||
_ => vec![],
|
||||
});
|
||||
}
|
||||
|
||||
TextContent::Text(self.node_text_content().into_boxed_str())
|
||||
}
|
||||
|
||||
fn restyle_damage(self) -> RestyleDamage {
|
||||
// We need the underlying node to potentially access the parent in the
|
||||
// case of text nodes. This is safe as long as we don't let the parent
|
||||
// escape and never access its descendants.
|
||||
let mut node = unsafe { self.unsafe_get() };
|
||||
|
||||
// If this is a text node, use the parent element, since that's what
|
||||
// controls our style.
|
||||
if node.is_text_node() {
|
||||
node = node.parent_node().unwrap();
|
||||
debug_assert!(node.is_element());
|
||||
}
|
||||
|
||||
let damage = {
|
||||
let data = node.get_raw_data().unwrap();
|
||||
|
||||
if !data
|
||||
.layout_data
|
||||
.borrow()
|
||||
.flags
|
||||
.contains(crate::data::LayoutDataFlags::HAS_BEEN_TRAVERSED)
|
||||
{
|
||||
// We're reflowing a node that was styled for the first time and
|
||||
// has never been visited by layout. Return rebuild_and_reflow,
|
||||
// because that's what the code expects.
|
||||
RestyleDamage::rebuild_and_reflow()
|
||||
} else {
|
||||
data.style_data.element_data.borrow().damage
|
||||
}
|
||||
};
|
||||
|
||||
damage
|
||||
}
|
||||
}
|
||||
|
||||
pub enum TextContent {
|
||||
Text(Box<str>),
|
||||
GeneratedContent(Vec<ContentItem>),
|
||||
}
|
||||
|
||||
impl TextContent {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match *self {
|
||||
TextContent::Text(_) => false,
|
||||
TextContent::GeneratedContent(ref content) => content.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,18 +11,43 @@ name = "layout_thread"
|
|||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
app_units = "0.7"
|
||||
atomic_refcell = "0.1"
|
||||
crossbeam-channel = "0.3"
|
||||
embedder_traits = {path = "../embedder_traits"}
|
||||
euclid = "0.20"
|
||||
fnv = "1.0"
|
||||
fxhash = "0.2"
|
||||
gfx = {path = "../gfx"}
|
||||
gfx_traits = {path = "../gfx_traits"}
|
||||
histogram = "0.6.8"
|
||||
html5ever = "0.23"
|
||||
ipc-channel = "0.11"
|
||||
layout = {path = "../layout_2020", package = "layout_2020"}
|
||||
layout_traits = {path = "../layout_traits"}
|
||||
lazy_static = "1"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
time = "0.1.17"
|
||||
malloc_size_of = { path = "../malloc_size_of" }
|
||||
metrics = {path = "../metrics"}
|
||||
msg = {path = "../msg"}
|
||||
net_traits = {path = "../net_traits"}
|
||||
parking_lot = {version = "0.8", features = ["nightly"]}
|
||||
profile_traits = {path = "../profile_traits"}
|
||||
range = {path = "../range"}
|
||||
rayon = "1"
|
||||
script = {path = "../script"}
|
||||
script_layout_interface = {path = "../script_layout_interface"}
|
||||
script_traits = {path = "../script_traits"}
|
||||
servo_url = {path = "../url"}
|
||||
selectors = { path = "../selectors" }
|
||||
serde_json = "1.0"
|
||||
servo_allocator = {path = "../allocator"}
|
||||
servo_arc = {path = "../servo_arc"}
|
||||
servo_atoms = {path = "../atoms"}
|
||||
servo_config = {path = "../config"}
|
||||
servo_geometry = {path = "../geometry"}
|
||||
servo_url = {path = "../url"}
|
||||
style = {path = "../style"}
|
||||
style_traits = {path = "../style_traits"}
|
||||
webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
|
||||
|
|
1550
components/layout_thread_2020/dom_wrapper.rs
Normal file
1550
components/layout_thread_2020/dom_wrapper.rs
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue