gfx: Implement box-shadow per CSS-BACKGROUNDS.

This commit is contained in:
Patrick Walton 2014-11-07 14:27:08 -08:00
parent 1bc2c8a639
commit 3ba0abd8ff
21 changed files with 766 additions and 31 deletions

View file

@ -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<AzFloat>,
transform: &Matrix2D<AzFloat>,
clip_rect: Option<&Rect<Au>>) {
clip_rect: Option<Rect<Au>>) {
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<BorderDisplayItem>),
GradientDisplayItemClass(Box<GradientDisplayItem>),
LineDisplayItemClass(Box<LineDisplayItem>),
BoxShadowDisplayItemClass(Box<BoxShadowDisplayItem>),
}
/// 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<Au>,
/// The offset of this shadow from the box.
pub offset: Point2D<Au>,
/// 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()

View file

@ -59,8 +59,7 @@ impl DisplayListOptimizer {
mut stacking_contexts: I)
where I: Iterator<&'a Arc<StackingContext>> {
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())
}
}

View file

@ -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<f32>,
/// The rectangle that this context encompasses in screen coordinates (pixels).
pub screen_rect: Rect<uint>,
/// The clipping rect for the stacking context as a whole.
pub clip_rect: Option<Rect<Au>>,
/// 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<Au>,
offset: &Point2D<Au>,
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<i32>;
}
impl ToAzureIntSize for Size2D<Au> {
fn to_azure_int_size(&self) -> Size2D<i32> {
Size2D(self.width.to_nearest_px() as i32, self.height.to_nearest_px() as i32)
}
}
impl ToAzureIntSize for Size2D<AzFloat> {
fn to_azure_int_size(&self) -> Size2D<i32> {
Size2D(self.width as i32, self.height as i32)
}
}
impl ToAzureIntSize for Size2D<i32> {
fn to_azure_int_size(&self) -> Size2D<i32> {
Size2D(self.width, self.height)
}
}
trait ToSideOffsetsPx {
fn to_float_px(&self) -> SideOffsets2D<AzFloat>;
}
@ -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<T>(&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<Au>) -> Path;
}
impl DrawTargetExtensions for DrawTarget {
fn create_rectangular_border_path<T>(&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<Au>) -> 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()
}
}

View file

@ -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,
};

View file

@ -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<Au>);
/// 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<Au>,
clip_rect: &Rect<Au>);
/// 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<Au>,
clip_rect: &Rect<Au>);
fn build_debug_borders_around_text_fragments(&self,
display_list: &mut DisplayList,
flow_origin: Point2D<Au>,
@ -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<Au>,
clip_rect: &Rect<Au>) {
// 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) => {

View file

@ -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:single_component_value>
<%self:longhand name="box-shadow">
use cssparser;
pub type SpecifiedValue = Vec<SpecifiedBoxShadow>;
#[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<specified::CSSColor>,
pub inset: bool,
}
pub mod computed_value {
use super::super::Au;
use super::super::super::computed;
pub type T = Vec<BoxShadow>;
#[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<SpecifiedValue,()> {
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<SpecifiedBoxShadow,()> {
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,
})
}
</%self:longhand>
}

View file

@ -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

View file

@ -0,0 +1,18 @@
<head>
<!-- Tests that box-shadow blur does something. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 100px;
left: 100px;
border: solid black 1px;
box-shadow: 10px 10px 5px 0px;
}
</style>
</head>
<body>
<section></section>
</body>

View file

@ -0,0 +1,18 @@
<head>
<!-- Tests that box-shadow blur does something. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 100px;
left: 100px;
border: solid black 1px;
box-shadow: 10px 10px;
}
</style>
</head>
<body>
<section></section>
</body>

View file

@ -0,0 +1,19 @@
<head>
<!-- Tests that the entire border-box is shadowed, not just the content box. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 100px;
left: 100px;
border-left: solid green 10px;
border-right: solid green 10px;
box-shadow: 20px 10px;
}
</style>
</head>
<body>
<section></section>
</body>

View file

@ -0,0 +1,30 @@
<head>
<!-- Tests that the entire border-box is shadowed, not just the content box. -->
<style>
section {
position: absolute;
}
#a {
width: 120px;
height: 100px;
top: 110px;
left: 120px;
background: black;
}
#b {
width: 100px;
height: 100px;
top: 100px;
left: 100px;
border-left: solid green 10px;
border-right: solid green 10px;
background: white;
}
</style>
</head>
<body>
<section id=a></section>
<section id=b></section>
</body>

View file

@ -0,0 +1,19 @@
<head>
<!-- Tests that the box-shadow color is correct if unspecified. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 100px;
left: 100px;
border: solid black 1px;
color: blue;
box-shadow: 30px 10px;
}
</style>
</head>
<body>
<section></section>
</body>

View file

@ -0,0 +1,18 @@
<head>
<!-- Tests that the box-shadow color is correct if unspecified. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 100px;
left: 100px;
border: solid black 1px;
box-shadow: 30px 10px blue;
}
</style>
</head>
<body>
<section></section>
</body>

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<head>
<!-- Tests that box-shadow inset works. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 100px;
left: 100px;
box-shadow: inset 50px 10px gold;
}
</style>
</head>
<body>
<section></section>
</body>

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<head>
<!-- Tests that box-shadow inset parsing quirks work properly. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 100px;
left: 100px;
border: solid black 1px;
box-shadow: inset 0 0 1em gold;
}
</style>
</head>
<body>
<section></section>
</body>

View file

@ -0,0 +1,21 @@
<!DOCTYPE html>
<head>
<!-- Tests that box-shadow inset parsing quirks work properly. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 100px;
left: 100px;
border: solid black 1px;
box-shadow: 0 0 1em gold inset;
}
</style>
</head>
<body>
<section></section>
</body>

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<head>
<!-- Tests that box-shadow inset works. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 100px;
left: 100px;
background: gold;
}
nav {
display: block;
position: absolute;
width: 50px;
height: 90px;
background: white;
left: 50px;
top: 10px;
}
</style>
</head>
<body>
<section><nav></nav></section>
</body>

View file

@ -0,0 +1,16 @@
<head>
<!-- Tests paint order of multiple box shadows. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 100px;
left: 100px;
box-shadow: -25px -25px purple, -50px -50px turquoise;
}
</style>
</head>
<body>
<section></section>
</body>

View file

@ -0,0 +1,30 @@
<head>
<!-- Tests paint order of multiple box shadows. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
}
#a {
top: 50px;
left: 50px;
background: turquoise;
}
#b {
top: 75px;
left: 75px;
background: purple;
}
#c {
top: 100px;
left: 100px;
background: white;
}
</style>
</head>
<body>
<section id=a></section>
<section id=b></section>
<section id=c></section>
</body>

View file

@ -0,0 +1,17 @@
<head>
<!-- Tests that spread works. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 100px;
left: 100px;
box-shadow: 0 0 0 25px;
}
</style>
</head>
<body>
<section></section>
</body>

View file

@ -0,0 +1,18 @@
<head>
<!-- Tests that spread works. -->
<style>
section {
position: absolute;
width: 100px;
height: 100px;
top: 75px;
left: 75px;
border: solid black 25px;
}
</style>
</head>
<body>
<section></section>
</body>