mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
gfx: Implement box-shadow
per CSS-BACKGROUNDS.
This commit is contained in:
parent
1bc2c8a639
commit
3ba0abd8ff
21 changed files with 766 additions and 31 deletions
|
@ -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()
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
18
tests/ref/box_shadow_blur_a.html
Normal file
18
tests/ref/box_shadow_blur_a.html
Normal 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>
|
||||
|
18
tests/ref/box_shadow_blur_ref.html
Normal file
18
tests/ref/box_shadow_blur_ref.html
Normal 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>
|
||||
|
19
tests/ref/box_shadow_border_box_a.html
Normal file
19
tests/ref/box_shadow_border_box_a.html
Normal 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>
|
||||
|
30
tests/ref/box_shadow_border_box_ref.html
Normal file
30
tests/ref/box_shadow_border_box_ref.html
Normal 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>
|
||||
|
||||
|
19
tests/ref/box_shadow_default_color_a.html
Normal file
19
tests/ref/box_shadow_default_color_a.html
Normal 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>
|
||||
|
18
tests/ref/box_shadow_default_color_ref.html
Normal file
18
tests/ref/box_shadow_default_color_ref.html
Normal 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>
|
||||
|
18
tests/ref/box_shadow_inset_a.html
Normal file
18
tests/ref/box_shadow_inset_a.html
Normal 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>
|
||||
|
20
tests/ref/box_shadow_inset_parsing_a.html
Normal file
20
tests/ref/box_shadow_inset_parsing_a.html
Normal 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>
|
||||
|
||||
|
21
tests/ref/box_shadow_inset_parsing_ref.html
Normal file
21
tests/ref/box_shadow_inset_parsing_ref.html
Normal 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>
|
||||
|
||||
|
||||
|
28
tests/ref/box_shadow_inset_ref.html
Normal file
28
tests/ref/box_shadow_inset_ref.html
Normal 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>
|
||||
|
||||
|
16
tests/ref/box_shadow_paint_order_a.html
Normal file
16
tests/ref/box_shadow_paint_order_a.html
Normal 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>
|
30
tests/ref/box_shadow_paint_order_ref.html
Normal file
30
tests/ref/box_shadow_paint_order_ref.html
Normal 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>
|
17
tests/ref/box_shadow_spread_a.html
Normal file
17
tests/ref/box_shadow_spread_a.html
Normal 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>
|
||||
|
18
tests/ref/box_shadow_spread_ref.html
Normal file
18
tests/ref/box_shadow_spread_ref.html
Normal 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>
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue