diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index 532b965d63e..df998d42da6 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -29,7 +29,7 @@ use script_traits::UntrustedNodeAddress; use servo_msg::compositor_msg::LayerId; use servo_net::image::base::Image; use servo_util::dlist as servo_dlist; -use servo_util::geometry::{mod, Au}; +use servo_util::geometry::{mod, Au, ZERO_POINT}; use servo_util::range::Range; use servo_util::smallvec::{SmallVec, SmallVec8}; use std::fmt; @@ -44,6 +44,11 @@ pub use azure::azure_hl::GradientStop; pub mod optimizer; +/// The factor that we multiply the blur radius by in order to inflate the boundaries of box shadow +/// display items. This ensures that the box shadow display item boundaries include all the +/// shadow's ink. +pub static BOX_SHADOW_INFLATION_FACTOR: i32 = 3; + /// An opaque handle to a node. The only safe operation that can be performed on this node is to /// compare it to another opaque handle or to another node. /// @@ -174,7 +179,7 @@ impl StackingContext { display_list: display_list, layer: layer, bounds: bounds, - clip_rect: bounds, + clip_rect: Rect(ZERO_POINT, bounds.size), z_index: z_index, opacity: opacity, } @@ -185,7 +190,7 @@ impl StackingContext { paint_context: &mut PaintContext, tile_bounds: &Rect, transform: &Matrix2D, - clip_rect: Option<&Rect>) { + clip_rect: Option>) { let temporary_draw_target = paint_context.get_or_create_temporary_draw_target(self.opacity); { @@ -194,6 +199,7 @@ impl StackingContext { font_ctx: &mut *paint_context.font_ctx, page_rect: paint_context.page_rect, screen_rect: paint_context.screen_rect, + clip_rect: clip_rect, transient_clip_rect: None, }; @@ -210,12 +216,9 @@ impl StackingContext { .sort_by(|this, other| this.z_index.cmp(&other.z_index)); // Set up our clip rect and transform. - match clip_rect { - None => {} - Some(clip_rect) => paint_subcontext.draw_push_clip(clip_rect), - } let old_transform = paint_subcontext.draw_target.get_transform(); paint_subcontext.draw_target.set_transform(transform); + paint_subcontext.push_clip_if_applicable(); // Steps 1 and 2: Borders and background for the root. for display_item in display_list.background_and_borders.iter() { @@ -243,7 +246,7 @@ impl StackingContext { positioned_kid.optimize_and_draw_into_context(&mut paint_subcontext, &new_tile_rect, &new_transform, - Some(&positioned_kid.clip_rect)) + Some(positioned_kid.clip_rect)) } } @@ -286,7 +289,7 @@ impl StackingContext { positioned_kid.optimize_and_draw_into_context(&mut paint_subcontext, &new_tile_rect, &new_transform, - Some(&positioned_kid.clip_rect)) + Some(positioned_kid.clip_rect)) } } @@ -296,14 +299,9 @@ impl StackingContext { } // Undo our clipping and transform. - if paint_subcontext.transient_clip_rect.is_some() { - paint_subcontext.draw_pop_clip(); - paint_subcontext.transient_clip_rect = None - } - paint_subcontext.draw_target.set_transform(&old_transform); - if clip_rect.is_some() { - paint_subcontext.draw_pop_clip() - } + paint_subcontext.remove_transient_clip_if_applicable(); + paint_subcontext.pop_clip_if_applicable(); + paint_subcontext.draw_target.set_transform(&old_transform) } paint_context.draw_temporary_draw_target_if_necessary(&temporary_draw_target, self.opacity) @@ -437,6 +435,7 @@ pub enum DisplayItem { BorderDisplayItemClass(Box), GradientDisplayItemClass(Box), LineDisplayItemClass(Box), + BoxShadowDisplayItemClass(Box), } /// Information common to all display items. @@ -571,6 +570,31 @@ pub struct LineDisplayItem { pub style: border_style::T } +/// Paints a box shadow per CSS-BACKGROUNDS. +#[deriving(Clone)] +pub struct BoxShadowDisplayItem { + /// Fields common to all display items. + pub base: BaseDisplayItem, + + /// The dimensions of the box that we're placing a shadow around. + pub box_bounds: Rect, + + /// The offset of this shadow from the box. + pub offset: Point2D, + + /// The color of this shadow. + pub color: Color, + + /// The blur radius for this shadow. + pub blur_radius: Au, + + /// The spread radius of this shadow. + pub spread_radius: Au, + + /// True if this shadow is inset; false if it's outset. + pub inset: bool, +} + pub enum DisplayItemIterator<'a> { EmptyDisplayItemIterator, ParentDisplayItemIterator(dlist::Items<'a,DisplayItem>), @@ -649,6 +673,15 @@ impl DisplayItem { line.color, line.style) } + + BoxShadowDisplayItemClass(ref box_shadow) => { + paint_context.draw_box_shadow(&box_shadow.box_bounds, + &box_shadow.offset, + box_shadow.color, + box_shadow.blur_radius, + box_shadow.spread_radius, + box_shadow.inset) + } } } @@ -660,6 +693,7 @@ impl DisplayItem { BorderDisplayItemClass(ref border) => &border.base, GradientDisplayItemClass(ref gradient) => &gradient.base, LineDisplayItemClass(ref line) => &line.base, + BoxShadowDisplayItemClass(ref box_shadow) => &box_shadow.base, } } @@ -671,6 +705,7 @@ impl DisplayItem { BorderDisplayItemClass(ref mut border) => &mut border.base, GradientDisplayItemClass(ref mut gradient) => &mut gradient.base, LineDisplayItemClass(ref mut line) => &mut line.base, + BoxShadowDisplayItemClass(ref mut box_shadow) => &mut box_shadow.base, } } @@ -697,6 +732,7 @@ impl fmt::Show for DisplayItem { BorderDisplayItemClass(_) => "Border", GradientDisplayItemClass(_) => "Gradient", LineDisplayItemClass(_) => "Line", + BoxShadowDisplayItemClass(_) => "BoxShadow", }, self.base().bounds, self.base().node.id() diff --git a/components/gfx/display_list/optimizer.rs b/components/gfx/display_list/optimizer.rs index 1397797d22a..6cf59632aef 100644 --- a/components/gfx/display_list/optimizer.rs +++ b/components/gfx/display_list/optimizer.rs @@ -59,8 +59,7 @@ impl DisplayListOptimizer { mut stacking_contexts: I) where I: Iterator<&'a Arc> { for stacking_context in stacking_contexts { - if self.visible_rect.intersects(&stacking_context.bounds) && - self.visible_rect.intersects(&stacking_context.clip_rect) { + if self.visible_rect.intersects(&stacking_context.bounds) { result_list.push_back((*stacking_context).clone()) } } diff --git a/components/gfx/paint_context.rs b/components/gfx/paint_context.rs index 0e3773c65a2..0748b3db928 100644 --- a/components/gfx/paint_context.rs +++ b/components/gfx/paint_context.rs @@ -5,13 +5,16 @@ //! Painting of display lists using Moz2D/Azure. use azure::azure::AzIntSize; -use azure::azure_hl::{B8G8R8A8, A8, Color, ColorPattern, ColorPatternRef, DrawOptions}; -use azure::azure_hl::{DrawSurfaceOptions, DrawTarget, ExtendClamp, GradientStop, Linear}; -use azure::azure_hl::{LinearGradientPattern, LinearGradientPatternRef, SourceOp, StrokeOptions}; +use azure::azure_hl::{A8, B8G8R8A8, Color, ColorPattern, ColorPatternRef, DrawOptions}; +use azure::azure_hl::{DrawSurfaceOptions, DrawTarget, ExtendClamp, GaussianBlurFilterType}; +use azure::azure_hl::{GaussianBlurInput, GradientStop, Linear, LinearGradientPattern}; +use azure::azure_hl::{LinearGradientPatternRef, Path, SourceOp, StdDeviationGaussianBlurAttribute}; +use azure::azure_hl::{StrokeOptions}; use azure::scaled_font::ScaledFont; use azure::{AZ_CAP_BUTT, AzFloat, struct__AzDrawOptions, struct__AzGlyph}; use azure::{struct__AzGlyphBuffer, struct__AzPoint, AzDrawTargetFillGlyphs}; -use display_list::{SidewaysLeft, SidewaysRight, TextDisplayItem, Upright, BorderRadii}; +use display_list::{BOX_SHADOW_INFLATION_FACTOR, BorderRadii, SidewaysLeft, SidewaysRight}; +use display_list::{TextDisplayItem, Upright}; use font_context::FontContext; use geom::matrix2d::Matrix2D; use geom::point::Point2D; @@ -22,7 +25,7 @@ use libc::size_t; use libc::types::common::c99::{uint16_t, uint32_t}; use png::{RGB8, RGBA8, K8, KA8}; use servo_net::image::base::Image; -use servo_util::geometry::Au; +use servo_util::geometry::{Au, MAX_RECT}; use servo_util::opts; use servo_util::range::Range; use std::default::Default; @@ -40,6 +43,8 @@ pub struct PaintContext<'a> { pub page_rect: Rect, /// The rectangle that this context encompasses in screen coordinates (pixels). pub screen_rect: Rect, + /// The clipping rect for the stacking context as a whole. + pub clip_rect: Option>, /// The current transient clipping rect, if any. A "transient clipping rect" is the clipping /// rect used by the last display item. We cache the last value so that we avoid pushing and /// popping clip rects unnecessarily. @@ -58,7 +63,7 @@ enum DashSize { DashedBorder = 3 } -impl<'a> PaintContext<'a> { +impl<'a> PaintContext<'a> { pub fn get_draw_target(&self) -> &DrawTarget { &self.draw_target } @@ -751,6 +756,108 @@ impl<'a> PaintContext<'a> { draw_options); self.draw_target.set_transform(&old_transform); } + + /// Draws a box shadow with the given boundaries, color, offset, blur radius, and spread + /// radius. `box_bounds` represents the boundaries of the box. + pub fn draw_box_shadow(&mut self, + box_bounds: &Rect, + offset: &Point2D, + color: Color, + blur_radius: Au, + spread_radius: Au, + inset: bool) { + // Remove both the transient clip and the stacking context clip, because we may need to + // draw outside the stacking context's clip. + self.remove_transient_clip_if_applicable(); + self.pop_clip_if_applicable(); + + // If we have blur, create a new draw target that's the same size as this tile, but with + // enough space around the edges to hold the entire blur. (If we don't do the latter, then + // there will be seams between tiles.) + // + // FIXME(pcwalton): This draw target might be larger than necessary and waste memory. + let side_inflation = (blur_radius * BOX_SHADOW_INFLATION_FACTOR).to_subpx().ceil() as i32; + let draw_target_transform = self.draw_target.get_transform(); + let temporary_draw_target; + if blur_radius > Au(0) { + let draw_target_size = self.draw_target.get_size(); + let draw_target_size = Size2D(draw_target_size.width, draw_target_size.height); + let inflated_draw_target_size = Size2D(draw_target_size.width + side_inflation * 2, + draw_target_size.height + side_inflation * 2); + temporary_draw_target = + self.draw_target.create_similar_draw_target(&inflated_draw_target_size, + self.draw_target.get_format()); + temporary_draw_target.set_transform( + &Matrix2D::identity().translate(side_inflation as AzFloat, + side_inflation as AzFloat) + .mul(&draw_target_transform)); + } else { + temporary_draw_target = self.draw_target.clone(); + } + + let shadow_bounds = box_bounds.translate(offset).inflate(spread_radius, spread_radius); + let path; + if inset { + path = temporary_draw_target.create_rectangular_border_path(&MAX_RECT, &shadow_bounds); + self.draw_target.push_clip(&self.draw_target.create_rectangular_path(box_bounds)) + } else { + path = temporary_draw_target.create_rectangular_path(&shadow_bounds); + self.draw_target.push_clip(&self.draw_target + .create_rectangular_border_path(&MAX_RECT, box_bounds)) + } + + temporary_draw_target.fill(&path, &ColorPattern::new(color), &DrawOptions::new(1.0, 0)); + + // Blur, if we need to. + if blur_radius > Au(0) { + // Go ahead and create the blur now. Despite the name, Azure's notion of `StdDeviation` + // describes the blur radius, not the sigma for the Gaussian blur. + let blur_filter = self.draw_target.create_filter(GaussianBlurFilterType); + blur_filter.set_attribute(StdDeviationGaussianBlurAttribute(blur_radius.to_subpx() as + AzFloat)); + blur_filter.set_input(GaussianBlurInput, &temporary_draw_target.snapshot()); + + // Blit the blur onto the tile. We undo the transforms here because we want to directly + // stack the temporary draw target onto the tile. + temporary_draw_target.set_transform(&Matrix2D::identity()); + self.draw_target.set_transform(&Matrix2D::identity()); + let temporary_draw_target_size = temporary_draw_target.get_size(); + self.draw_target + .draw_filter(blur_filter, + &Rect(Point2D(0.0, 0.0), + Size2D(temporary_draw_target_size.width as AzFloat, + temporary_draw_target_size.height as AzFloat)), + &Point2D(-side_inflation as AzFloat, -side_inflation as AzFloat), + DrawOptions::new(1.0, 0)); + self.draw_target.set_transform(&draw_target_transform); + } + + // Undo the draw target's clip. + self.draw_target.pop_clip(); + + // Push back the stacking context clip. + self.push_clip_if_applicable(); + } + + pub fn push_clip_if_applicable(&self) { + match self.clip_rect { + None => {} + Some(ref clip_rect) => self.draw_push_clip(clip_rect), + } + } + + pub fn pop_clip_if_applicable(&self) { + if self.clip_rect.is_some() { + self.draw_pop_clip() + } + } + + pub fn remove_transient_clip_if_applicable(&mut self) { + if self.transient_clip_rect.is_some() { + self.draw_pop_clip(); + self.transient_clip_rect = None + } + } } pub trait ToAzurePoint { @@ -785,6 +892,28 @@ impl ToAzureSize for AzIntSize { } } +trait ToAzureIntSize { + fn to_azure_int_size(&self) -> Size2D; +} + +impl ToAzureIntSize for Size2D { + fn to_azure_int_size(&self) -> Size2D { + Size2D(self.width.to_nearest_px() as i32, self.height.to_nearest_px() as i32) + } +} + +impl ToAzureIntSize for Size2D { + fn to_azure_int_size(&self) -> Size2D { + Size2D(self.width as i32, self.height as i32) + } +} + +impl ToAzureIntSize for Size2D { + fn to_azure_int_size(&self) -> Size2D { + Size2D(self.width, self.height) + } +} + trait ToSideOffsetsPx { fn to_float_px(&self) -> SideOffsets2D; } @@ -890,3 +1019,65 @@ impl ScaledFontExtensionMethods for ScaledFont { } } } + +trait DrawTargetExtensions { + /// Creates and returns a path that represents a rectangular border. Like this: + /// + /// ```text + /// +--------------------------------+ + /// |################################| + /// |#######+---------------------+##| + /// |#######| |##| + /// |#######+---------------------+##| + /// |################################| + /// +--------------------------------+ + /// ``` + fn create_rectangular_border_path(&self, outer_rect: &T, inner_rect: &T) + -> Path + where T: ToAzureRect; + + /// Creates and returns a path that represents a rectangle. + fn create_rectangular_path(&self, rect: &Rect) -> Path; +} + +impl DrawTargetExtensions for DrawTarget { + fn create_rectangular_border_path(&self, outer_rect: &T, inner_rect: &T) + -> Path + where T: ToAzureRect { + // +-----------+ + // |2 |1 + // | | + // | +---+---+ + // | |9 |6 |5, 10 + // | | | | + // | +---+ | + // | 8 7 | + // | | + // +-----------+ + // 3 4 + + let (outer_rect, inner_rect) = (outer_rect.to_azure_rect(), inner_rect.to_azure_rect()); + let path_builder = self.create_path_builder(); + path_builder.move_to(Point2D(outer_rect.max_x(), outer_rect.origin.y)); // 1 + path_builder.line_to(Point2D(outer_rect.origin.x, outer_rect.origin.y)); // 2 + path_builder.line_to(Point2D(outer_rect.origin.x, outer_rect.max_y())); // 3 + path_builder.line_to(Point2D(outer_rect.max_x(), outer_rect.max_y())); // 4 + path_builder.line_to(Point2D(outer_rect.max_x(), inner_rect.origin.y)); // 5 + path_builder.line_to(Point2D(inner_rect.max_x(), inner_rect.origin.y)); // 6 + path_builder.line_to(Point2D(inner_rect.max_x(), inner_rect.max_y())); // 7 + path_builder.line_to(Point2D(inner_rect.origin.x, inner_rect.max_y())); // 8 + path_builder.line_to(inner_rect.origin); // 9 + path_builder.line_to(Point2D(outer_rect.max_x(), inner_rect.origin.y)); // 10 + path_builder.finish() + } + + fn create_rectangular_path(&self, rect: &Rect) -> Path { + let path_builder = self.create_path_builder(); + path_builder.move_to(rect.origin.to_azure_point()); + path_builder.line_to(Point2D(rect.max_x(), rect.origin.y).to_azure_point()); + path_builder.line_to(Point2D(rect.max_x(), rect.max_y()).to_azure_point()); + path_builder.line_to(Point2D(rect.origin.x, rect.max_y()).to_azure_point()); + path_builder.finish() + } +} + diff --git a/components/gfx/paint_task.rs b/components/gfx/paint_task.rs index 03fefad0867..2f1dad3b436 100644 --- a/components/gfx/paint_task.rs +++ b/components/gfx/paint_task.rs @@ -509,6 +509,7 @@ impl WorkerThread { font_ctx: &mut self.font_context, page_rect: tile.page_rect, screen_rect: tile.screen_rect, + clip_rect: None, transient_clip_rect: None, }; diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index db0a39605eb..b11a14033c0 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -24,12 +24,14 @@ use util::{OpaqueNodeMethods, ToGfxColor}; use geom::approxeq::ApproxEq; use geom::{Point2D, Rect, Size2D, SideOffsets2D}; use gfx::color; -use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass, DisplayItem}; -use gfx::display_list::{DisplayList, GradientDisplayItem, GradientDisplayItemClass, GradientStop}; -use gfx::display_list::{ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem, BorderRadii}; -use gfx::display_list::{LineDisplayItemClass, SidewaysLeft, SidewaysRight}; -use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, StackingContext}; -use gfx::display_list::{TextDisplayItem, TextDisplayItemClass, Upright}; +use gfx::display_list::{BOX_SHADOW_INFLATION_FACTOR, BaseDisplayItem, BorderDisplayItem}; +use gfx::display_list::{BorderDisplayItemClass, BorderRadii, BoxShadowDisplayItem}; +use gfx::display_list::{BoxShadowDisplayItemClass, DisplayItem, DisplayList}; +use gfx::display_list::{GradientDisplayItem, GradientDisplayItemClass}; +use gfx::display_list::{GradientStop, ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem}; +use gfx::display_list::{LineDisplayItemClass, SidewaysLeft}; +use gfx::display_list::{SidewaysRight, SolidColorDisplayItem, SolidColorDisplayItemClass}; +use gfx::display_list::{StackingContext, TextDisplayItem, TextDisplayItemClass, Upright}; use gfx::paint_task::PaintLayer; use servo_msg::compositor_msg::{FixedPosition, Scrollable}; use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg}; @@ -112,12 +114,24 @@ pub trait FragmentDisplayListBuilding { level: StackingLevel, clip_rect: &Rect); + /// Adds the display items necessary to paint the outline of this fragment to the display list + /// if necessary. fn build_display_list_for_outline_if_applicable(&self, style: &ComputedValues, display_list: &mut DisplayList, bounds: &Rect, clip_rect: &Rect); + /// Adds the display items necessary to paint the box shadow of this fragment to the display + /// list if necessary. + fn build_display_list_for_box_shadow_if_applicable(&self, + style: &ComputedValues, + list: &mut DisplayList, + layout_context: &LayoutContext, + level: StackingLevel, + absolute_bounds: &Rect, + clip_rect: &Rect); + fn build_debug_borders_around_text_fragments(&self, display_list: &mut DisplayList, flow_origin: Point2D, @@ -413,6 +427,32 @@ impl FragmentDisplayListBuilding for Fragment { display_list.push(gradient_display_item, level) } + fn build_display_list_for_box_shadow_if_applicable(&self, + style: &ComputedValues, + list: &mut DisplayList, + _layout_context: &LayoutContext, + level: StackingLevel, + absolute_bounds: &Rect, + clip_rect: &Rect) { + // NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back). + for box_shadow in style.get_effects().box_shadow.iter().rev() { + let inflation = box_shadow.spread_radius + box_shadow.blur_radius * + BOX_SHADOW_INFLATION_FACTOR; + let bounds = + absolute_bounds.translate(&Point2D(box_shadow.offset_x, box_shadow.offset_y)) + .inflate(inflation, inflation); + list.push(BoxShadowDisplayItemClass(box BoxShadowDisplayItem { + base: BaseDisplayItem::new(bounds, self.node, *clip_rect), + box_bounds: *absolute_bounds, + color: style.resolve_color(box_shadow.color).to_gfx_color(), + offset: Point2D(box_shadow.offset_x, box_shadow.offset_y), + blur_radius: box_shadow.blur_radius, + spread_radius: box_shadow.spread_radius, + inset: box_shadow.inset, + }), level); + } + } + fn build_display_list_for_borders_if_applicable(&self, style: &ComputedValues, display_list: &mut DisplayList, @@ -590,6 +630,34 @@ impl FragmentDisplayListBuilding for Fragment { let level = StackingLevel::from_background_and_border_level(background_and_border_level); + // Add a shadow to the list, if applicable. + match self.inline_context { + Some(ref inline_context) => { + for style in inline_context.styles.iter().rev() { + self.build_display_list_for_box_shadow_if_applicable( + &**style, + display_list, + layout_context, + level, + &absolute_fragment_bounds, + clip_rect); + } + } + None => {} + } + match self.specific { + ScannedTextFragment(_) => {}, + _ => { + self.build_display_list_for_box_shadow_if_applicable( + &*self.style, + display_list, + layout_context, + level, + &absolute_fragment_bounds, + clip_rect); + } + } + // Add the background to the list, if applicable. match self.inline_context { Some(ref inline_context) => { diff --git a/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako index 634c93b0419..9453b32f05c 100644 --- a/components/style/properties/mod.rs.mako +++ b/components/style/properties/mod.rs.mako @@ -1296,6 +1296,7 @@ pub mod longhands { ${single_keyword("box-sizing", "content-box border-box")} + // Box-shadow, etc. ${new_style_struct("Effects", is_inherited=False)} <%self:single_component_value name="opacity"> @@ -1326,6 +1327,148 @@ pub mod longhands { } } + + <%self:longhand name="box-shadow"> + use cssparser; + + pub type SpecifiedValue = Vec; + + #[deriving(Clone)] + pub struct SpecifiedBoxShadow { + pub offset_x: specified::Length, + pub offset_y: specified::Length, + pub blur_radius: specified::Length, + pub spread_radius: specified::Length, + pub color: Option, + pub inset: bool, + } + + pub mod computed_value { + use super::super::Au; + use super::super::super::computed; + + pub type T = Vec; + + #[deriving(Clone, PartialEq)] + pub struct BoxShadow { + pub offset_x: Au, + pub offset_y: Au, + pub blur_radius: Au, + pub spread_radius: Au, + pub color: computed::CSSColor, + pub inset: bool, + } + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + Vec::new() + } + + pub fn parse(input: &[ComponentValue], _: &Url) -> Result { + match one_component_value(input) { + Ok(&Ident(ref value)) if value.as_slice().eq_ignore_ascii_case("none") => { + return Ok(Vec::new()) + } + _ => {} + } + parse_slice_comma_separated(input, parse_one_box_shadow) + } + + pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) + -> computed_value::T { + value.into_iter().map(|value| { + computed_value::BoxShadow { + offset_x: computed::compute_Au(value.offset_x, context), + offset_y: computed::compute_Au(value.offset_y, context), + blur_radius: computed::compute_Au(value.blur_radius, context), + spread_radius: computed::compute_Au(value.spread_radius, context), + color: value.color.unwrap_or(cssparser::CurrentColor), + inset: value.inset, + } + }).collect() + } + + fn parse_one_box_shadow(iter: ParserIter) -> Result { + let mut lengths = [specified::Au_(Au(0)), ..4]; + let mut lengths_parsed = false; + let mut color = None; + let mut inset = false; + + loop { + match iter.next() { + Some(&Ident(ref value)) if value.eq_ignore_ascii_case("inset") && !inset => { + inset = true; + continue + } + Some(value) => { + // Try to parse a length. + match specified::Length::parse(value) { + Ok(the_length) if !lengths_parsed => { + lengths[0] = the_length; + let mut length_parsed_count = 1; + while length_parsed_count < 4 { + match iter.next() { + Some(value) => { + match specified::Length::parse(value) { + Ok(the_length) => { + lengths[length_parsed_count] = the_length; + } + Err(_) => { + iter.push_back(value); + break + } + } + } + None => break, + } + length_parsed_count += 1; + } + + // The first two lengths must be specified. + if length_parsed_count < 2 { + return Err(()) + } + + lengths_parsed = true; + continue + } + Ok(_) => return Err(()), + Err(()) => {} + } + + // Try to parse a color. + match specified::CSSColor::parse(value) { + Ok(the_color) if color.is_none() => { + color = Some(the_color); + continue + } + Ok(_) => return Err(()), + Err(()) => {} + } + + iter.push_back(value); + break + } + None => break, + } + } + + // Lengths must be specified. + if !lengths_parsed { + return Err(()) + } + + Ok(SpecifiedBoxShadow { + offset_x: lengths[0], + offset_y: lengths[1], + blur_radius: lengths[2], + spread_radius: lengths[3], + color: color, + inset: inset, + }) + } + } diff --git a/tests/ref/basic.list b/tests/ref/basic.list index cfb21ad0d3f..49a0ff9e959 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -200,3 +200,10 @@ fragment=top != ../html/acid2.html acid2_ref.html == text_indent_a.html text_indent_ref.html == word_spacing_a.html word_spacing_ref.html == overflow_wrap_a.html overflow_wrap_ref.html +!= box_shadow_blur_a.html box_shadow_blur_ref.html +== box_shadow_border_box_a.html box_shadow_border_box_ref.html +== box_shadow_default_color_a.html box_shadow_default_color_ref.html +== box_shadow_paint_order_a.html box_shadow_paint_order_ref.html +== box_shadow_spread_a.html box_shadow_spread_ref.html +== box_shadow_inset_a.html box_shadow_inset_ref.html +== box_shadow_inset_parsing_a.html box_shadow_inset_parsing_ref.html diff --git a/tests/ref/box_shadow_blur_a.html b/tests/ref/box_shadow_blur_a.html new file mode 100644 index 00000000000..b49d2fa7837 --- /dev/null +++ b/tests/ref/box_shadow_blur_a.html @@ -0,0 +1,18 @@ + + + + + +
+ + diff --git a/tests/ref/box_shadow_blur_ref.html b/tests/ref/box_shadow_blur_ref.html new file mode 100644 index 00000000000..9bc6a693306 --- /dev/null +++ b/tests/ref/box_shadow_blur_ref.html @@ -0,0 +1,18 @@ + + + + + +
+ + diff --git a/tests/ref/box_shadow_border_box_a.html b/tests/ref/box_shadow_border_box_a.html new file mode 100644 index 00000000000..e64f478dcc7 --- /dev/null +++ b/tests/ref/box_shadow_border_box_a.html @@ -0,0 +1,19 @@ + + + + + +
+ + diff --git a/tests/ref/box_shadow_border_box_ref.html b/tests/ref/box_shadow_border_box_ref.html new file mode 100644 index 00000000000..4b2ff005733 --- /dev/null +++ b/tests/ref/box_shadow_border_box_ref.html @@ -0,0 +1,30 @@ + + + + + +
+
+ + + diff --git a/tests/ref/box_shadow_default_color_a.html b/tests/ref/box_shadow_default_color_a.html new file mode 100644 index 00000000000..fe36f41752d --- /dev/null +++ b/tests/ref/box_shadow_default_color_a.html @@ -0,0 +1,19 @@ + + + + + +
+ + diff --git a/tests/ref/box_shadow_default_color_ref.html b/tests/ref/box_shadow_default_color_ref.html new file mode 100644 index 00000000000..3fff8f71892 --- /dev/null +++ b/tests/ref/box_shadow_default_color_ref.html @@ -0,0 +1,18 @@ + + + + + +
+ + diff --git a/tests/ref/box_shadow_inset_a.html b/tests/ref/box_shadow_inset_a.html new file mode 100644 index 00000000000..c6076a6c730 --- /dev/null +++ b/tests/ref/box_shadow_inset_a.html @@ -0,0 +1,18 @@ + + + + + + +
+ + diff --git a/tests/ref/box_shadow_inset_parsing_a.html b/tests/ref/box_shadow_inset_parsing_a.html new file mode 100644 index 00000000000..eb21c825107 --- /dev/null +++ b/tests/ref/box_shadow_inset_parsing_a.html @@ -0,0 +1,20 @@ + + + + + + +
+ + + diff --git a/tests/ref/box_shadow_inset_parsing_ref.html b/tests/ref/box_shadow_inset_parsing_ref.html new file mode 100644 index 00000000000..3811f315635 --- /dev/null +++ b/tests/ref/box_shadow_inset_parsing_ref.html @@ -0,0 +1,21 @@ + + + + + + +
+ + + + diff --git a/tests/ref/box_shadow_inset_ref.html b/tests/ref/box_shadow_inset_ref.html new file mode 100644 index 00000000000..48ee086ea3d --- /dev/null +++ b/tests/ref/box_shadow_inset_ref.html @@ -0,0 +1,28 @@ + + + + + + +
+ + + diff --git a/tests/ref/box_shadow_paint_order_a.html b/tests/ref/box_shadow_paint_order_a.html new file mode 100644 index 00000000000..8506bfc309d --- /dev/null +++ b/tests/ref/box_shadow_paint_order_a.html @@ -0,0 +1,16 @@ + + + + + +
+ diff --git a/tests/ref/box_shadow_paint_order_ref.html b/tests/ref/box_shadow_paint_order_ref.html new file mode 100644 index 00000000000..1510d570d71 --- /dev/null +++ b/tests/ref/box_shadow_paint_order_ref.html @@ -0,0 +1,30 @@ + + + + + +
+
+
+ diff --git a/tests/ref/box_shadow_spread_a.html b/tests/ref/box_shadow_spread_a.html new file mode 100644 index 00000000000..6e1e45a2738 --- /dev/null +++ b/tests/ref/box_shadow_spread_a.html @@ -0,0 +1,17 @@ + + + + + +
+ + diff --git a/tests/ref/box_shadow_spread_ref.html b/tests/ref/box_shadow_spread_ref.html new file mode 100644 index 00000000000..bb0cc767044 --- /dev/null +++ b/tests/ref/box_shadow_spread_ref.html @@ -0,0 +1,18 @@ + + + + + +
+ + +