layout: Rewrite clipping to be a two-phase process that takes physical

border box positions and transforms into account.

Clipping region computation now follows a simple process: (1) in the
parent's coordinate system, parents store appropriate clipping regions
into children; (2) each child moves its clipping region to its own
coordinate system if necessary.

Because clipping region computation is now based on stacking-relative
border box positions and the `transform_rect` method, it can handle
`position: relative` offsets and more types of transforms, such as
scaling.

Improves etsy.com.

Closes #13753.
This commit is contained in:
Patrick Walton 2016-10-13 14:48:30 -07:00 committed by Michael Howell
parent 759185abe0
commit 7df456e8ac
9 changed files with 251 additions and 162 deletions

View file

@ -977,6 +977,11 @@ impl ClippingRegion {
}).collect(),
}
}
#[inline]
pub fn is_max(&self) -> bool {
self.main == max_rect() && self.complex.is_empty()
}
}
impl fmt::Debug for ClippingRegion {

View file

@ -47,8 +47,8 @@ use gfx::display_list::{ClippingRegion, StackingContext};
use gfx_traits::LayerId;
use gfx_traits::print_tree::PrintTree;
use layout_debug;
use model::{self, IntrinsicISizes, MarginCollapseInfo};
use model::{CollapsibleMargins, MaybeAuto, specified, specified_or_none};
use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo, MaybeAuto};
use model::{specified, specified_or_none};
use rustc_serialize::{Encodable, Encoder};
use script_layout_interface::restyle_damage::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW};
use script_layout_interface::restyle_damage::REPOSITION;
@ -57,7 +57,7 @@ use std::cmp::{max, min};
use std::fmt;
use std::sync::Arc;
use style::computed_values::{border_collapse, box_sizing, display, float, overflow_x, overflow_y};
use style::computed_values::{position, text_align, transform, transform_style};
use style::computed_values::{position, text_align, transform_style};
use style::context::{SharedStyleContext, StyleContext};
use style::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
use style::properties::ServoComputedValues;
@ -2070,12 +2070,9 @@ impl Flow for BlockFlow {
self.base.position.size.to_physical(self.base.writing_mode);
// Compute the origin and clipping rectangle for children.
//
// `clip` is in the child coordinate system.
let mut clip;
let origin_for_children;
let relative_offset = relative_offset.to_physical(self.base.writing_mode);
let is_stacking_context = self.fragment.establishes_stacking_context();
if is_stacking_context {
let origin_for_children = if is_stacking_context {
// We establish a stacking context, so the position of our children is vertically
// correct, but has to be adjusted to accommodate horizontal margins. (Note the
// calculation involving `position` below and recall that inline-direction flow
@ -2083,13 +2080,10 @@ impl Flow for BlockFlow {
//
// FIXME(pcwalton): Is this vertical-writing-direction-safe?
let margin = self.fragment.margin.to_physical(self.base.writing_mode);
origin_for_children = Point2D::new(-margin.left, Au(0));
clip = self.base.clip.translate(&-self.base.stacking_relative_position);
Point2D::new(-margin.left, Au(0))
} else {
let relative_offset = relative_offset.to_physical(self.base.writing_mode);
origin_for_children = self.base.stacking_relative_position + relative_offset;
clip = self.base.clip.clone();
}
self.base.stacking_relative_position + relative_offset
};
let stacking_relative_position_of_display_port_for_children =
if is_stacking_context || self.is_root() {
@ -2120,9 +2114,17 @@ impl Flow for BlockFlow {
.early_absolute_position_info
.relative_containing_block_mode,
CoordinateSystem::Own);
self.fragment.adjust_clipping_region_for_children(
&mut clip,
&stacking_relative_border_box);
// Our parent set our `clip` field to the clipping region in its coordinate system. Change
// it to our coordinate system.
self.switch_coordinate_system_if_necessary();
self.fragment.adjust_clip_for_style(&mut self.base.clip, &stacking_relative_border_box);
// Compute the clipping region for children, taking our `overflow` properties and so forth
// into account.
let mut clip_for_children = self.base.clip.clone();
self.fragment.adjust_clipping_region_for_children(&mut clip_for_children,
&stacking_relative_border_box);
// Process children.
for kid in self.base.child_iter_mut() {
@ -2166,50 +2168,18 @@ impl Flow for BlockFlow {
flow::mut_base(kid).late_absolute_position_info =
late_absolute_position_info_for_children;
let clip = if kid.is_block_like() {
let mut clip = clip.clone();
let kid = kid.as_block();
// TODO(notriddle): To properly support transformations, we either need
// non-rectangular clipping regions in display lists, or clipping
// regions in terms of the parent coordinate system instead of the
// child coordinate system.
//
// This is a workaround for a common idiom of transform: translate().
if let Some(ref operations) = kid.fragment.style().get_effects().transform.0 {
for operation in operations {
match *operation {
transform::ComputedOperation::Translate(tx, ty, _) => {
// N.B. When the clipping value comes from us, it
// shouldn't be transformed.
let tx = if let overflow_x::T::hidden = kid.fragment.style().get_box()
.overflow_x {
Au(0)
} else {
model::specified(tx, kid.base.block_container_inline_size)
};
let ty = if let overflow_x::T::hidden = kid.fragment.style().get_box()
.overflow_y.0 {
Au(0)
} else {
model::specified(
ty,
kid.base.block_container_explicit_block_size.unwrap_or(Au(0))
)
};
let off = Point2D::new(tx, ty);
clip = clip.translate(&-off);
}
_ => {}
};
}
}
clip
} else {
clip.clone()
};
flow::mut_base(kid).clip = clip;
flow::mut_base(kid).stacking_relative_position_of_display_port =
stacking_relative_position_of_display_port_for_children;
// This clipping region is in our coordinate system. The child will fix it up to be in
// its own coordinate system by itself if necessary.
//
// Rationale: If the child is absolutely positioned, it hasn't been positioned at this
// point (as absolutely-positioned flows position themselves in
// `compute_absolute_position()`). Therefore, we don't always know what the child's
// coordinate system is here. So we store the clipping region in our coordinate system
// for now; the child will move it later if needed.
flow::mut_base(kid).clip = clip_for_children.clone()
}
self.base.restyle_damage.remove(REPOSITION)

View file

@ -15,7 +15,7 @@ use azure::azure_hl::Color;
use block::{BlockFlow, BlockStackingContextType};
use canvas_traits::{CanvasData, CanvasMsg, FromLayoutMsg};
use context::SharedLayoutContext;
use euclid::{Matrix4D, Point2D, Point3D, Radians, Rect, SideOffsets2D, Size2D};
use euclid::{Matrix4D, Point2D, Radians, Rect, SideOffsets2D, Size2D};
use flex::FlexFlow;
use flow::{BaseFlow, Flow, IS_ABSOLUTELY_POSITIONED};
use flow_ref;
@ -242,7 +242,7 @@ pub trait FragmentDisplayListBuilding {
clip: &ClippingRegion,
stacking_relative_display_port: &Rect<Au>);
/// Adjusts the clipping region for descendants of this fragment as appropriate.
/// Adjusts the clipping region for all descendants of this fragment as appropriate.
fn adjust_clipping_region_for_children(&self,
current_clip: &mut ClippingRegion,
stacking_relative_border_box: &Rect<Au>);
@ -293,6 +293,9 @@ pub trait FragmentDisplayListBuilding {
scroll_policy: ScrollPolicy,
mode: StackingContextCreationMode)
-> StackingContext;
/// Returns the 4D matrix representing this fragment's transform.
fn transform_matrix(&self, stacking_relative_border_box: &Rect<Au>) -> Matrix4D<f32>;
}
fn handle_overlapping_radii(size: &Size2D<Au>, radii: &BorderRadii<Au>) -> BorderRadii<Au> {
@ -1107,12 +1110,9 @@ impl FragmentDisplayListBuilding for Fragment {
}
}
// Calculate the clip rect. If there's nothing to render at all, don't even construct
// display list items.
let mut clip = (*clip).clone();
self.adjust_clip_for_style(&mut clip, &stacking_relative_border_box);
// Check the clip rect. If there's nothing to render at all, don't even construct display
// list items.
let empty_rect = !clip.might_intersect_rect(&stacking_relative_border_box);
if self.is_primary_fragment() && !empty_rect {
// Add shadows, background, borders, and outlines, if applicable.
if let Some(ref inline_context) = self.inline_context {
@ -1122,13 +1122,13 @@ impl FragmentDisplayListBuilding for Fragment {
&*node.style,
display_list_section,
&stacking_relative_border_box,
&clip);
clip);
self.build_display_list_for_box_shadow_if_applicable(
state,
&*node.style,
display_list_section,
&stacking_relative_border_box,
&clip);
clip);
let mut style = node.style.clone();
properties::modify_border_style_for_inline_sides(
@ -1141,13 +1141,13 @@ impl FragmentDisplayListBuilding for Fragment {
border_painting_mode,
&stacking_relative_border_box,
display_list_section,
&clip);
clip);
self.build_display_list_for_outline_if_applicable(
state,
&*node.style,
&stacking_relative_border_box,
&clip);
clip);
}
}
@ -1156,22 +1156,22 @@ impl FragmentDisplayListBuilding for Fragment {
&*self.style,
display_list_section,
&stacking_relative_border_box,
&clip);
clip);
self.build_display_list_for_box_shadow_if_applicable(state,
&*self.style,
display_list_section,
&stacking_relative_border_box,
&clip);
clip);
self.build_display_list_for_borders_if_applicable(state,
&*self.style,
border_painting_mode,
&stacking_relative_border_box,
display_list_section,
&clip);
clip);
self.build_display_list_for_outline_if_applicable(state,
&*self.style,
&stacking_relative_border_box,
&clip);
clip);
}
}
@ -1181,11 +1181,11 @@ impl FragmentDisplayListBuilding for Fragment {
self.build_display_items_for_selection_if_necessary(state,
&stacking_relative_border_box,
display_list_section,
&clip);
clip);
}
if empty_rect {
return;
return
}
debug!("Fragment::build_display_list: intersected. Adding display item...");
@ -1193,12 +1193,10 @@ impl FragmentDisplayListBuilding for Fragment {
// Create special per-fragment-type display items.
self.build_fragment_type_specific_display_items(state,
&stacking_relative_border_box,
&clip);
clip);
if opts::get().show_debug_fragment_borders {
self.build_debug_borders_around_fragment(state,
&stacking_relative_border_box,
&clip);
self.build_debug_borders_around_fragment(state, &stacking_relative_border_box, clip)
}
}
@ -1417,56 +1415,7 @@ impl FragmentDisplayListBuilding for Fragment {
}
};
let mut transform = Matrix4D::identity();
if let Some(ref operations) = self.style().get_effects().transform.0 {
let transform_origin = self.style().get_effects().transform_origin;
let transform_origin =
Point3D::new(model::specified(transform_origin.horizontal,
border_box.size.width).to_f32_px(),
model::specified(transform_origin.vertical,
border_box.size.height).to_f32_px(),
transform_origin.depth.to_f32_px());
let pre_transform = Matrix4D::create_translation(transform_origin.x,
transform_origin.y,
transform_origin.z);
let post_transform = Matrix4D::create_translation(-transform_origin.x,
-transform_origin.y,
-transform_origin.z);
for operation in operations {
let matrix = match *operation {
transform::ComputedOperation::Rotate(ax, ay, az, theta) => {
let theta = 2.0f32 * f32::consts::PI - theta.radians();
Matrix4D::create_rotation(ax, ay, az, Radians::new(theta))
}
transform::ComputedOperation::Perspective(d) => {
create_perspective_matrix(d)
}
transform::ComputedOperation::Scale(sx, sy, sz) => {
Matrix4D::create_scale(sx, sy, sz)
}
transform::ComputedOperation::Translate(tx, ty, tz) => {
let tx = model::specified(tx, border_box.size.width).to_f32_px();
let ty = model::specified(ty, border_box.size.height).to_f32_px();
let tz = tz.to_f32_px();
Matrix4D::create_translation(tx, ty, tz)
}
transform::ComputedOperation::Matrix(m) => {
m.to_gfx_matrix()
}
transform::ComputedOperation::Skew(theta_x, theta_y) => {
Matrix4D::create_skew(Radians::new(theta_x.radians()),
Radians::new(theta_y.radians()))
}
};
transform = transform.pre_mul(&matrix);
}
transform = pre_transform.pre_mul(&transform).pre_mul(&post_transform);
}
let transform = self.transform_matrix(&border_box);
let perspective = match self.style().get_effects().perspective {
LengthOrNone::Length(d) => {
let perspective_origin = self.style().get_effects().perspective_origin;
@ -1545,13 +1494,9 @@ impl FragmentDisplayListBuilding for Fragment {
return
}
// Account for style-specified `clip`.
self.adjust_clip_for_style(current_clip, stacking_relative_border_box);
let overflow_x = self.style.get_box().overflow_x;
let overflow_y = self.style.get_box().overflow_y.0;
if let (overflow_x::T::visible, overflow_x::T::visible) = (overflow_x, overflow_y) {
if overflow_x == overflow_x::T::visible && overflow_y == overflow_x::T::visible {
return
}
@ -1562,7 +1507,8 @@ impl FragmentDisplayListBuilding for Fragment {
stacking_relative_border_box
}
overflow_clip_box::T::content_box => {
overflow_clip_rect_owner = self.stacking_relative_content_box(stacking_relative_border_box);
overflow_clip_rect_owner =
self.stacking_relative_content_box(stacking_relative_border_box);
&overflow_clip_rect_owner
}
};
@ -1570,8 +1516,8 @@ impl FragmentDisplayListBuilding for Fragment {
// Clip according to the values of `overflow-x` and `overflow-y`.
//
// FIXME(pcwalton): This may be more complex than it needs to be, since it seems to be
// impossible with the computed value rules as they are to have `overflow-x: visible` with
// `overflow-y: <scrolling>` or vice versa!
// impossible with the computed value rules as they are to have `overflow-x: visible`
// with `overflow-y: <scrolling>` or vice versa!
if let overflow_x::T::hidden = self.style.get_box().overflow_x {
let mut bounds = current_clip.bounding_rect();
let max_x = cmp::min(bounds.max_x(), overflow_clip_rect.max_x());
@ -1590,7 +1536,8 @@ impl FragmentDisplayListBuilding for Fragment {
let border_radii = build_border_radius(stacking_relative_border_box,
self.style.get_border());
if !border_radii.is_square() {
current_clip.intersect_with_rounded_rect(stacking_relative_border_box, &border_radii)
current_clip.intersect_with_rounded_rect(stacking_relative_border_box,
&border_radii)
}
}
@ -1731,6 +1678,64 @@ impl FragmentDisplayListBuilding for Fragment {
clip_mode: BoxShadowClipMode::None,
}));
}
fn transform_matrix(&self, stacking_relative_border_box: &Rect<Au>) -> Matrix4D<f32> {
let mut transform = Matrix4D::identity();
let operations = match self.style.get_effects().transform.0 {
None => return transform,
Some(ref operations) => operations,
};
let transform_origin = &self.style.get_effects().transform_origin;
let transform_origin_x = model::specified(transform_origin.horizontal,
stacking_relative_border_box.size
.width).to_f32_px();
let transform_origin_y = model::specified(transform_origin.vertical,
stacking_relative_border_box.size
.height).to_f32_px();
let transform_origin_z = transform_origin.depth.to_f32_px();
let pre_transform = Matrix4D::create_translation(transform_origin_x,
transform_origin_y,
transform_origin_z);
let post_transform = Matrix4D::create_translation(-transform_origin_x,
-transform_origin_y,
-transform_origin_z);
for operation in operations {
let matrix = match *operation {
transform::ComputedOperation::Rotate(ax, ay, az, theta) => {
let theta = 2.0f32 * f32::consts::PI - theta.radians();
Matrix4D::create_rotation(ax, ay, az, Radians::new(theta))
}
transform::ComputedOperation::Perspective(d) => {
create_perspective_matrix(d)
}
transform::ComputedOperation::Scale(sx, sy, sz) => {
Matrix4D::create_scale(sx, sy, sz)
}
transform::ComputedOperation::Translate(tx, ty, tz) => {
let tx =
model::specified(tx, stacking_relative_border_box.size.width).to_f32_px();
let ty =
model::specified(ty, stacking_relative_border_box.size.height).to_f32_px();
let tz = tz.to_f32_px();
Matrix4D::create_translation(tx, ty, tz)
}
transform::ComputedOperation::Matrix(m) => {
m.to_gfx_matrix()
}
transform::ComputedOperation::Skew(theta_x, theta_y) => {
Matrix4D::create_skew(Radians::new(theta_x.radians()),
Radians::new(theta_y.radians()))
}
};
transform = transform.pre_mul(&matrix);
}
pre_transform.pre_mul(&transform).pre_mul(&post_transform)
}
}
pub trait BlockFlowDisplayListBuilding {
@ -1738,6 +1743,16 @@ pub trait BlockFlowDisplayListBuilding {
fn build_display_list_for_block(&mut self,
state: &mut DisplayListBuildState,
border_painting_mode: BorderPaintingMode);
/// Changes this block's clipping region from its parent's coordinate system to its own
/// coordinate system if necessary (i.e. if this block is a stacking context).
///
/// The clipping region is initially in each block's parent's coordinate system because the
/// parent of each block does not have enough information to determine what the child's
/// coordinate system is on its own. Specifically, if the child is absolutely positioned, the
/// parent does not know where the child's absolute position is at the time it assigns clipping
/// regions, because flows compute their own absolute positions.
fn switch_coordinate_system_if_necessary(&mut self);
}
impl BlockFlowDisplayListBuilding for BlockFlow {
@ -1848,14 +1863,6 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
};
// Add the box that starts the block context.
let clip_owner;
let clip = if establishes_stacking_context {
clip_owner = self.base.clip.translate(&-self.base.stacking_relative_position);
&clip_owner
} else {
&self.base.clip
};
self.fragment
.build_display_list(state,
&self.base.stacking_relative_position,
@ -1867,11 +1874,61 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
.relative_containing_block_mode,
border_painting_mode,
background_border_section,
clip,
&self.base.clip,
&self.base.stacking_relative_position_of_display_port);
self.base.build_display_items_for_debugging_tint(state, self.fragment.node);
}
fn switch_coordinate_system_if_necessary(&mut self) {
// Avoid overflows!
if self.base.clip.is_max() {
return
}
if !self.fragment.establishes_stacking_context() {
return
}
let stacking_relative_border_box =
self.fragment.stacking_relative_border_box(&self.base.stacking_relative_position,
&self.base
.early_absolute_position_info
.relative_containing_block_size,
self.base
.early_absolute_position_info
.relative_containing_block_mode,
CoordinateSystem::Parent);
self.base.clip = self.base.clip.translate(&-stacking_relative_border_box.origin);
// Account for `transform`, if applicable.
if self.fragment.style.get_effects().transform.0.is_none() {
return
}
let transform = match self.fragment
.transform_matrix(&stacking_relative_border_box)
.inverse() {
Some(transform) => transform,
None => {
// Singular matrix. Ignore it.
return
}
};
// FIXME(pcwalton): This is inaccurate: not all transforms are 2D, and not all clips are
// axis-aligned.
let bounding_rect = self.base.clip.bounding_rect();
let bounding_rect = Rect::new(Point2D::new(bounding_rect.origin.x.to_f32_px(),
bounding_rect.origin.y.to_f32_px()),
Size2D::new(bounding_rect.size.width.to_f32_px(),
bounding_rect.size.height.to_f32_px()));
let clip_rect = transform.to_2d().transform_rect(&bounding_rect);
let clip_rect = Rect::new(Point2D::new(Au::from_f32_px(clip_rect.origin.x),
Au::from_f32_px(clip_rect.origin.y)),
Size2D::new(Au::from_f32_px(clip_rect.size.width),
Au::from_f32_px(clip_rect.size.height)));
self.base.clip = ClippingRegion::from_rect(&clip_rect)
}
}
pub trait InlineFlowDisplayListBuilding {
@ -2117,3 +2174,4 @@ pub enum StackingContextCreationMode {
PseudoPositioned,
PseudoFloat,
}

View file

@ -935,7 +935,9 @@ pub struct BaseFlow {
/// assignment.
pub late_absolute_position_info: LateAbsolutePositionInfo,
/// The clipping region for this flow and its descendants, in layer coordinates.
/// The clipping region for this flow and its descendants, in the coordinate system of the
/// nearest ancestor stacking context. If this flow itself represents a stacking context, then
/// this is in the flow's own coordinate system.
pub clip: ClippingRegion,
/// The stacking-relative position of the display port.

View file

@ -7,8 +7,7 @@
use app_units::Au;
use block::AbsoluteAssignBSizesTraversal;
use context::{LayoutContext, SharedLayoutContext};
use display_list_builder::{FragmentDisplayListBuilding, InlineFlowDisplayListBuilding};
use display_list_builder::DisplayListBuildState;
use display_list_builder::{DisplayListBuildState, InlineFlowDisplayListBuilding};
use euclid::{Point2D, Size2D};
use floats::{FloatKind, Floats, PlacementInfo};
use flow::{self, BaseFlow, Flow, FlowClass, ForceNonfloatedFlag, IS_ABSOLUTELY_POSITIONED};
@ -1547,15 +1546,11 @@ impl Flow for InlineFlow {
CoordinateSystem::Parent);
let stacking_relative_content_box =
fragment.stacking_relative_content_box(&stacking_relative_border_box);
let mut clip = self.base.clip.clone();
fragment.adjust_clipping_region_for_children(&mut clip,
&stacking_relative_border_box);
let is_positioned = fragment.is_positioned();
match fragment.specific {
SpecificFragmentInfo::InlineBlock(ref mut info) => {
let flow = flow_ref::deref_mut(&mut info.flow_ref);
flow::mut_base(flow).clip = clip;
let block_flow = flow.as_mut_block();
block_flow.base.late_absolute_position_info =
self.base.late_absolute_position_info;
@ -1573,10 +1568,13 @@ impl Flow for InlineFlow {
stacking_relative_content_box.origin;
block_flow.base.stacking_relative_position_of_display_port =
self.base.stacking_relative_position_of_display_port;
// Write the clip in our coordinate system into the child flow. (The kid will
// fix it up to be in its own coordinate system if necessary.)
block_flow.base.clip = self.base.clip.clone()
}
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) => {
let flow = flow_ref::deref_mut(&mut info.flow_ref);
flow::mut_base(flow).clip = clip;
let block_flow = flow.as_mut_block();
block_flow.base.late_absolute_position_info =
self.base.late_absolute_position_info;
@ -1585,11 +1583,12 @@ impl Flow for InlineFlow {
stacking_relative_border_box.origin;
block_flow.base.stacking_relative_position_of_display_port =
self.base.stacking_relative_position_of_display_port;
// As above, this is in our coordinate system for now.
block_flow.base.clip = self.base.clip.clone()
}
SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
let flow = flow_ref::deref_mut(&mut info.flow_ref);
flow::mut_base(flow).clip = clip;
let block_flow = flow.as_mut_block();
block_flow.base.late_absolute_position_info =
self.base.late_absolute_position_info;
@ -1605,6 +1604,9 @@ impl Flow for InlineFlow {
stacking_relative_border_box.origin;
block_flow.base.stacking_relative_position_of_display_port =
self.base.stacking_relative_position_of_display_port;
// As above, this is in our coordinate system for now.
block_flow.base.clip = self.base.clip.clone()
}
_ => {}
}