From 52b9951cad26041434aabd60593c2f78ea00a22b Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 8 Dec 2014 23:29:41 -0800 Subject: [PATCH] =?UTF-8?q?layout:=20Implement=20`outline`=20per=20CSS=202?= =?UTF-8?q?.1=20=C2=A7=2018.4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `invert` is not yet supported. Objects that get layers will not yet display outlines properly. This is because our overflow calculation doesn't take styles into account and because layers are always anchored to the top left of the border box. Since fixing this is work that is not related to outline *per se* I'm leaving that to a followup and making a note in the code. --- components/gfx/display_list/mod.rs | 19 +++++- components/gfx/display_list/optimizer.rs | 1 + components/layout/display_list_builder.rs | 58 +++++++++++++++-- components/layout/flow.rs | 5 ++ components/style/properties/mod.rs.mako | 78 +++++++++++++++++++++++ tests/ref/basic.list | 2 + tests/ref/outlines_simple_a.html | 21 ++++++ tests/ref/outlines_simple_ref.html | 27 ++++++++ tests/ref/outlines_wrap_a.html | 18 ++++++ tests/ref/outlines_wrap_ref.html | 19 ++++++ 10 files changed, 242 insertions(+), 6 deletions(-) create mode 100644 tests/ref/outlines_simple_a.html create mode 100644 tests/ref/outlines_simple_ref.html create mode 100644 tests/ref/outlines_wrap_a.html create mode 100644 tests/ref/outlines_wrap_ref.html diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index 417162c54a1..532b965d63e 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -75,6 +75,8 @@ pub struct DisplayList { pub floats: DList, /// All other content. pub content: DList, + /// Outlines: step 10. + pub outlines: DList, /// Child stacking contexts. pub children: DList>, } @@ -88,6 +90,7 @@ impl DisplayList { block_backgrounds_and_borders: DList::new(), floats: DList::new(), content: DList::new(), + outlines: DList::new(), children: DList::new(), } } @@ -102,12 +105,14 @@ impl DisplayList { &mut other.block_backgrounds_and_borders); servo_dlist::append_from(&mut self.floats, &mut other.floats); servo_dlist::append_from(&mut self.content, &mut other.content); + servo_dlist::append_from(&mut self.outlines, &mut other.outlines); servo_dlist::append_from(&mut self.children, &mut other.children); } /// Merges all display items from all non-float stacking levels to the `float` stacking level. #[inline] pub fn form_float_pseudo_stacking_context(&mut self) { + servo_dlist::prepend_from(&mut self.floats, &mut self.outlines); servo_dlist::prepend_from(&mut self.floats, &mut self.content); servo_dlist::prepend_from(&mut self.floats, &mut self.block_backgrounds_and_borders); servo_dlist::prepend_from(&mut self.floats, &mut self.background_and_borders); @@ -129,6 +134,9 @@ impl DisplayList { for display_item in self.content.iter() { result.push((*display_item).clone()) } + for display_item in self.outlines.iter() { + result.push((*display_item).clone()) + } result } } @@ -282,7 +290,10 @@ impl StackingContext { } } - // TODO(pcwalton): Step 10: Outlines. + // Step 10: Outlines. + for display_item in display_list.outlines.iter() { + display_item.draw_into_context(&mut paint_subcontext) + } // Undo our clipping and transform. if paint_subcontext.transient_clip_rect.is_some() { @@ -349,6 +360,12 @@ impl StackingContext { // Iterate through display items in reverse stacking order. Steps here refer to the // painting steps in CSS 2.1 Appendix E. // + // Step 10: Outlines. + hit_test_in_list(point, result, topmost_only, self.display_list.outlines.iter().rev()); + if topmost_only && !result.is_empty() { + return + } + // Steps 9 and 8: Positioned descendants with nonnegative z-indices. for kid in self.display_list.children.iter().rev() { if kid.z_index < 0 { diff --git a/components/gfx/display_list/optimizer.rs b/components/gfx/display_list/optimizer.rs index cb5703b6c3e..1397797d22a 100644 --- a/components/gfx/display_list/optimizer.rs +++ b/components/gfx/display_list/optimizer.rs @@ -35,6 +35,7 @@ impl DisplayListOptimizer { display_list.block_backgrounds_and_borders.iter()); self.add_in_bounds_display_items(&mut result.floats, display_list.floats.iter()); self.add_in_bounds_display_items(&mut result.content, display_list.content.iter()); + self.add_in_bounds_display_items(&mut result.outlines, display_list.outlines.iter()); self.add_in_bounds_stacking_contexts(&mut result.children, display_list.children.iter()); result } diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index b2296c721fc..db0a39605eb 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -112,6 +112,12 @@ pub trait FragmentDisplayListBuilding { level: StackingLevel, clip_rect: &Rect); + fn build_display_list_for_outline_if_applicable(&self, + style: &ComputedValues, + display_list: &mut DisplayList, + bounds: &Rect, + clip_rect: &Rect); + fn build_debug_borders_around_text_fragments(&self, display_list: &mut DisplayList, flow_origin: Point2D, @@ -439,6 +445,40 @@ impl FragmentDisplayListBuilding for Fragment { }), level); } + fn build_display_list_for_outline_if_applicable(&self, + style: &ComputedValues, + display_list: &mut DisplayList, + bounds: &Rect, + clip_rect: &Rect) { + let width = style.get_outline().outline_width; + if width == Au(0) { + return + } + + let outline_style = style.get_outline().outline_style; + if outline_style == border_style::none { + return + } + + // Outlines are not accounted for in the dimensions of the border box, so adjust the + // absolute bounds. + let mut bounds = *bounds; + bounds.origin.x = bounds.origin.x - width; + bounds.origin.y = bounds.origin.y - width; + bounds.size.width = bounds.size.width + width + width; + bounds.size.height = bounds.size.height + width + width; + + // Append the outline to the display list. + let color = style.resolve_color(style.get_outline().outline_color).to_gfx_color(); + display_list.outlines.push_back(BorderDisplayItemClass(box BorderDisplayItem { + base: BaseDisplayItem::new(bounds, self.node, *clip_rect), + border_widths: SideOffsets2D::new_all_same(width), + color: SideOffsets2D::new_all_same(color), + style: SideOffsets2D::new_all_same(outline_style), + radius: Default::default(), + })) + } + fn build_debug_borders_around_text_fragments(&self, display_list: &mut DisplayList, flow_origin: Point2D, @@ -578,9 +618,7 @@ impl FragmentDisplayListBuilding for Fragment { } } - // Add a border, if applicable. - // - // TODO: Outlines. + // Add a border and outlines, if applicable. match self.inline_context { Some(ref inline_context) => { for style in inline_context.styles.iter().rev() { @@ -590,6 +628,11 @@ impl FragmentDisplayListBuilding for Fragment { &absolute_fragment_bounds, level, clip_rect); + self.build_display_list_for_outline_if_applicable( + &**style, + display_list, + &absolute_fragment_bounds, + clip_rect); } } None => {} @@ -603,6 +646,11 @@ impl FragmentDisplayListBuilding for Fragment { &absolute_fragment_bounds, level, clip_rect); + self.build_display_list_for_outline_if_applicable( + &*self.style, + display_list, + &absolute_fragment_bounds, + clip_rect); } } } @@ -870,8 +918,8 @@ impl BlockFlowDisplayListBuilding for BlockFlow { let stacking_context = self.create_stacking_context(display_list, Some(Arc::new(PaintLayer::new(self.layer_id(0), - transparent, - scroll_policy)))); + transparent, + scroll_policy)))); self.base.display_list_building_result = StackingContextResult(stacking_context) } diff --git a/components/layout/flow.rs b/components/layout/flow.rs index 598a8717cdb..832ca187f91 100644 --- a/components/layout/flow.rs +++ b/components/layout/flow.rs @@ -1177,6 +1177,11 @@ impl<'a> MutableFlowUtils for &'a mut Flow + 'a { /// Assumption: Absolute descendants have had their overflow calculated. fn store_overflow(self, _: &LayoutContext) { let my_position = mut_base(self).position; + + // FIXME(pcwalton): We should calculate overflow on a per-fragment basis, because their + // styles can affect overflow regions. Consider `box-shadow`, `outline`, etc.--anything + // that can draw outside the border box. For now we assume overflow is the border box, but + // that is wrong. let mut overflow = my_position; if self.is_block_container() { diff --git a/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako index 5dc02d89a9d..0c00c772871 100644 --- a/components/style/properties/mod.rs.mako +++ b/components/style/properties/mod.rs.mako @@ -355,6 +355,38 @@ pub mod longhands { % endfor + ${new_style_struct("Outline", is_inherited=False)} + + // TODO(pcwalton): `invert` + ${predefined_type("outline-color", "CSSColor", "CurrentColor")} + + <%self:single_component_value name="outline-style"> + pub use super::border_top_style::{get_initial_value, to_computed_value}; + pub type SpecifiedValue = super::border_top_style::SpecifiedValue; + pub mod computed_value { + pub type T = super::super::border_top_style::computed_value::T; + } + pub fn from_component_value(value: &ComponentValue, base_url: &Url) + -> Result { + match value { + &Ident(ref ident) if ident.eq_ignore_ascii_case("hidden") => { + // `hidden` is not a valid value. + Err(()) + } + _ => super::border_top_style::from_component_value(value, base_url) + } + } + + + <%self:longhand name="outline-width"> + pub use super::border_top_width::{get_initial_value, parse}; + pub use computed::compute_Au as to_computed_value; + pub type SpecifiedValue = super::border_top_width::SpecifiedValue; + pub mod computed_value { + pub type T = super::super::border_top_width::computed_value::T; + } + + ${new_style_struct("PositionOffsets", is_inherited=False)} % for side in ["top", "right", "bottom", "left"]: @@ -1544,6 +1576,52 @@ pub mod shorthands { }) + <%self:shorthand name="outline" sub_properties="outline-color outline-style outline-width"> + let (mut color, mut style, mut width, mut any) = (None, None, None, false); + for component_value in input.skip_whitespace() { + if color.is_none() { + match specified::CSSColor::parse(component_value) { + Ok(c) => { + color = Some(c); + any = true; + continue + } + Err(()) => {} + } + } + if style.is_none() { + match border_top_style::from_component_value(component_value, base_url) { + Ok(s) => { + style = Some(s); + any = true; + continue + } + Err(()) => {} + } + } + if width.is_none() { + match parse_border_width(component_value, base_url) { + Ok(w) => { + width = Some(w); + any = true; + continue + } + Err(()) => {} + } + } + return Err(()) + } + if any { + Ok(Longhands { + outline_color: color, + outline_style: style, + outline_width: width, + }) + } else { + Err(()) + } + + <%self:shorthand name="font" sub_properties="font-style font-variant font-weight font-size line-height font-family"> let mut iter = input.skip_whitespace(); diff --git a/tests/ref/basic.list b/tests/ref/basic.list index 52a115fa547..f06e562d94b 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -191,3 +191,5 @@ fragment=top != ../html/acid2.html acid2_ref.html == text_transform_uppercase_a.html text_transform_uppercase_ref.html == text_transform_lowercase_a.html text_transform_lowercase_ref.html == text_transform_capitalize_a.html text_transform_capitalize_ref.html +== outlines_simple_a.html outlines_simple_ref.html +== outlines_wrap_a.html outlines_wrap_ref.html diff --git a/tests/ref/outlines_simple_a.html b/tests/ref/outlines_simple_a.html new file mode 100644 index 00000000000..2dfc2e9a632 --- /dev/null +++ b/tests/ref/outlines_simple_a.html @@ -0,0 +1,21 @@ + + + + +
+ + + diff --git a/tests/ref/outlines_simple_ref.html b/tests/ref/outlines_simple_ref.html new file mode 100644 index 00000000000..994661d7851 --- /dev/null +++ b/tests/ref/outlines_simple_ref.html @@ -0,0 +1,27 @@ + + + + +
+ + + + diff --git a/tests/ref/outlines_wrap_a.html b/tests/ref/outlines_wrap_a.html new file mode 100644 index 00000000000..d34a797d456 --- /dev/null +++ b/tests/ref/outlines_wrap_a.html @@ -0,0 +1,18 @@ + + + + + +
I like truffles
+ + diff --git a/tests/ref/outlines_wrap_ref.html b/tests/ref/outlines_wrap_ref.html new file mode 100644 index 00000000000..db5c08893b5 --- /dev/null +++ b/tests/ref/outlines_wrap_ref.html @@ -0,0 +1,19 @@ + + + + + +
I like truffles
+ + +