auto merge of #4299 : pcwalton/servo/outline, r=mbrubeck

`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.

r? @SimonSapin
This commit is contained in:
bors-servo 2014-12-11 16:45:56 -07:00
commit d31237f343
10 changed files with 242 additions and 6 deletions

View file

@ -75,6 +75,8 @@ pub struct DisplayList {
pub floats: DList<DisplayItem>, pub floats: DList<DisplayItem>,
/// All other content. /// All other content.
pub content: DList<DisplayItem>, pub content: DList<DisplayItem>,
/// Outlines: step 10.
pub outlines: DList<DisplayItem>,
/// Child stacking contexts. /// Child stacking contexts.
pub children: DList<Arc<StackingContext>>, pub children: DList<Arc<StackingContext>>,
} }
@ -88,6 +90,7 @@ impl DisplayList {
block_backgrounds_and_borders: DList::new(), block_backgrounds_and_borders: DList::new(),
floats: DList::new(), floats: DList::new(),
content: DList::new(), content: DList::new(),
outlines: DList::new(),
children: DList::new(), children: DList::new(),
} }
} }
@ -102,12 +105,14 @@ impl DisplayList {
&mut other.block_backgrounds_and_borders); &mut other.block_backgrounds_and_borders);
servo_dlist::append_from(&mut self.floats, &mut other.floats); 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.content, &mut other.content);
servo_dlist::append_from(&mut self.outlines, &mut other.outlines);
servo_dlist::append_from(&mut self.children, &mut other.children); 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. /// Merges all display items from all non-float stacking levels to the `float` stacking level.
#[inline] #[inline]
pub fn form_float_pseudo_stacking_context(&mut self) { 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.content);
servo_dlist::prepend_from(&mut self.floats, &mut self.block_backgrounds_and_borders); 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); 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() { for display_item in self.content.iter() {
result.push((*display_item).clone()) result.push((*display_item).clone())
} }
for display_item in self.outlines.iter() {
result.push((*display_item).clone())
}
result 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. // Undo our clipping and transform.
if paint_subcontext.transient_clip_rect.is_some() { 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 // Iterate through display items in reverse stacking order. Steps here refer to the
// painting steps in CSS 2.1 Appendix E. // 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. // Steps 9 and 8: Positioned descendants with nonnegative z-indices.
for kid in self.display_list.children.iter().rev() { for kid in self.display_list.children.iter().rev() {
if kid.z_index < 0 { if kid.z_index < 0 {

View file

@ -35,6 +35,7 @@ impl DisplayListOptimizer {
display_list.block_backgrounds_and_borders.iter()); 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.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.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()); self.add_in_bounds_stacking_contexts(&mut result.children, display_list.children.iter());
result result
} }

View file

@ -112,6 +112,12 @@ pub trait FragmentDisplayListBuilding {
level: StackingLevel, level: StackingLevel,
clip_rect: &Rect<Au>); clip_rect: &Rect<Au>);
fn build_display_list_for_outline_if_applicable(&self,
style: &ComputedValues,
display_list: &mut DisplayList,
bounds: &Rect<Au>,
clip_rect: &Rect<Au>);
fn build_debug_borders_around_text_fragments(&self, fn build_debug_borders_around_text_fragments(&self,
display_list: &mut DisplayList, display_list: &mut DisplayList,
flow_origin: Point2D<Au>, flow_origin: Point2D<Au>,
@ -439,6 +445,40 @@ impl FragmentDisplayListBuilding for Fragment {
}), level); }), level);
} }
fn build_display_list_for_outline_if_applicable(&self,
style: &ComputedValues,
display_list: &mut DisplayList,
bounds: &Rect<Au>,
clip_rect: &Rect<Au>) {
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, fn build_debug_borders_around_text_fragments(&self,
display_list: &mut DisplayList, display_list: &mut DisplayList,
flow_origin: Point2D<Au>, flow_origin: Point2D<Au>,
@ -578,9 +618,7 @@ impl FragmentDisplayListBuilding for Fragment {
} }
} }
// Add a border, if applicable. // Add a border and outlines, if applicable.
//
// TODO: Outlines.
match self.inline_context { match self.inline_context {
Some(ref inline_context) => { Some(ref inline_context) => {
for style in inline_context.styles.iter().rev() { for style in inline_context.styles.iter().rev() {
@ -590,6 +628,11 @@ impl FragmentDisplayListBuilding for Fragment {
&absolute_fragment_bounds, &absolute_fragment_bounds,
level, level,
clip_rect); clip_rect);
self.build_display_list_for_outline_if_applicable(
&**style,
display_list,
&absolute_fragment_bounds,
clip_rect);
} }
} }
None => {} None => {}
@ -603,6 +646,11 @@ impl FragmentDisplayListBuilding for Fragment {
&absolute_fragment_bounds, &absolute_fragment_bounds,
level, level,
clip_rect); clip_rect);
self.build_display_list_for_outline_if_applicable(
&*self.style,
display_list,
&absolute_fragment_bounds,
clip_rect);
} }
} }
} }

View file

@ -1177,6 +1177,11 @@ impl<'a> MutableFlowUtils for &'a mut Flow + 'a {
/// Assumption: Absolute descendants have had their overflow calculated. /// Assumption: Absolute descendants have had their overflow calculated.
fn store_overflow(self, _: &LayoutContext) { fn store_overflow(self, _: &LayoutContext) {
let my_position = mut_base(self).position; 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; let mut overflow = my_position;
if self.is_block_container() { if self.is_block_container() {

View file

@ -355,6 +355,38 @@ pub mod longhands {
</%self:longhand> </%self:longhand>
% endfor % 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<SpecifiedValue,()> {
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:single_component_value>
<%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;
}
</%self:longhand>
${new_style_struct("PositionOffsets", is_inherited=False)} ${new_style_struct("PositionOffsets", is_inherited=False)}
% for side in ["top", "right", "bottom", "left"]: % for side in ["top", "right", "bottom", "left"]:
@ -1544,6 +1576,52 @@ pub mod shorthands {
}) })
</%self:shorthand> </%self:shorthand>
<%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>
<%self:shorthand name="font" sub_properties="font-style font-variant font-weight <%self:shorthand name="font" sub_properties="font-style font-variant font-weight
font-size line-height font-family"> font-size line-height font-family">
let mut iter = input.skip_whitespace(); let mut iter = input.skip_whitespace();

View file

@ -191,3 +191,5 @@ fragment=top != ../html/acid2.html acid2_ref.html
== text_transform_uppercase_a.html text_transform_uppercase_ref.html == text_transform_uppercase_a.html text_transform_uppercase_ref.html
== text_transform_lowercase_a.html text_transform_lowercase_ref.html == text_transform_lowercase_a.html text_transform_lowercase_ref.html
== text_transform_capitalize_a.html text_transform_capitalize_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

View file

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<style>
html, body {
margin: 0;
}
section {
position: relative;
width: 92px;
height: 92px;
border: solid red 2px;
outline: solid blue 2px;
top: 2px;
left: 2px;
}
</style>
<body>
<section></section>
</body>
</html>

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<style>
html, body {
margin: 0;
}
section {
position: relative;
width: 96px;
height: 96px;
border: solid blue 2px;
}
nav {
position: absolute;
width: 92px;
height: 92px;
border: solid red 2px;
top: 0;
left: 0;
}
</style>
<body>
<section><nav></nav></section>
</body>
</html>

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<!-- Tests that outlines draw on all four sides on word wrapped edges. -->
<style>
section {
width: 100px;
height: 100px;
border: solid black 2px;
font-size: 36px;
}
span {
outline: solid red 2px;
}
</style>
<body>
<section><span>I like truffles</span></section>
</body>
</html>

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<!-- Tests that outlines draw on all four sides on word wrapped edges. -->
<style>
section {
width: 100px;
height: 100px;
border: solid black 2px;
font-size: 36px;
}
span {
outline: solid red 2px;
}
</style>
<body>
<section><span>I like </span><span>truffles</span></section>
</body>
</html>