Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gfx: Implement most of `box-shadow` per CSS-BACKGROUNDS. #3940

Closed
wants to merge 6 commits into from
Next

gfx: Implement most of `box-shadow` per CSS-BACKGROUNDS.

All features of the `box-shadow` property are supported, including
spread and multiple shadows, with the exception of `inset`. However,
spread currently looks ugly when combined with blur. This is the result
of a present limitation in the Azure API that will necessitate
modifications to Azure. Because that will involve modifying all Azure
backends, I have left that work to a followup.
  • Loading branch information
pcwalton committed Dec 8, 2014
commit b1ede28d96c394f9151ac846c77cbae44bd53c9c
@@ -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.
///
@@ -420,6 +425,7 @@ pub enum DisplayItem {
BorderDisplayItemClass(Box<BorderDisplayItem>),
GradientDisplayItemClass(Box<GradientDisplayItem>),
LineDisplayItemClass(Box<LineDisplayItem>),
BoxShadowDisplayItemClass(Box<BoxShadowDisplayItem>),

/// A pseudo-display item that exists only so that queries like `ContentBoxQuery` and
/// `ContentBoxesQuery` can be answered.
@@ -561,6 +567,28 @@ 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,
}

pub enum DisplayItemIterator<'a> {
EmptyDisplayItemIterator,
ParentDisplayItemIterator(dlist::Items<'a,DisplayItem>),
@@ -640,6 +668,15 @@ impl DisplayItem {
line.style)
}

BoxShadowDisplayItemClass(ref box_shadow) => {
render_context.draw_box_shadow(&box_shadow.base.bounds,
&box_shadow.box_bounds,
&box_shadow.offset,
box_shadow.color,
box_shadow.blur_radius,
box_shadow.spread_radius)
}

PseudoDisplayItemClass(_) => {}
}
}
@@ -652,6 +689,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,
PseudoDisplayItemClass(ref base) => &**base,
}
}
@@ -664,6 +702,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,
PseudoDisplayItemClass(ref mut base) => &mut **base,
}
}
@@ -691,6 +730,7 @@ impl fmt::Show for DisplayItem {
BorderDisplayItemClass(_) => "Border",
GradientDisplayItemClass(_) => "Gradient",
LineDisplayItemClass(_) => "Line",
BoxShadowDisplayItemClass(_) => "BoxShadow",
PseudoDisplayItemClass(_) => "Pseudo",
},
self.base().bounds,
@@ -7,11 +7,13 @@
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::{LinearGradientPattern, LinearGradientPatternRef, OverOp, SourceOp};
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 +24,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_AU};
use servo_util::opts;
use servo_util::range::Range;
use std::default::Default;
@@ -44,6 +46,8 @@ pub struct PaintContext<'a> {
/// rect used by the last display item. We cache the last value so that we avoid pushing and
/// popping clip rects unnecessarily.
pub transient_clip_rect: Option<Rect<Au>>,
/// The scale factor.
pub scale: AzFloat,
}

enum Direction {
@@ -739,6 +743,89 @@ 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. `bounds` is just a rectangle that must be large enough to encompass all the ink in
/// this box shadow. `box_bounds` represents the boundaries of the box itself.
///
/// TODO(pcwalton): Add support for `inset`.
pub fn draw_box_shadow(&self,
bounds: &Rect<Au>,
box_bounds: &Rect<Au>,
offset: &Point2D<Au>,
color: Color,
blur_radius: Au,
spread_radius: Au) {
let sigma = blur_radius.to_subpx() as f32 * 2.0;
let format = self.draw_target.get_format();
let scaled_box_size =
Size2D((bounds.size.width.to_subpx() as f32 * self.scale) as i32,
(bounds.size.height.to_subpx() as f32 * self.scale) as i32);
let shadow_draw_target =
self.draw_target.create_shadow_draw_target(&scaled_box_size.to_azure_int_size(),
format,
sigma);
let matrix: Matrix2D<AzFloat> = Matrix2D::identity().scale(self.scale, self.scale);
shadow_draw_target.set_transform(&matrix);

let shadow_draw_target_offset =
Point2D((blur_radius + spread_radius) * BOX_SHADOW_INFLATION_FACTOR,
(blur_radius + spread_radius) * BOX_SHADOW_INFLATION_FACTOR);
let shadow_draw_target_box_bounds = Rect(shadow_draw_target_offset, box_bounds.size);

// FIXME(pcwalton): This is a pretty ugly way to paint with a spread when a blur is
// present. We will need to add support for it to Azure to actually draw it well.
let shadow_draw_target_box_bounds = shadow_draw_target_box_bounds.inflate(spread_radius,
spread_radius);

shadow_draw_target.fill_rect(&shadow_draw_target_box_bounds.to_azure_rect(),
ColorPatternRef(&ColorPattern::new(color)),
None);

let shadow_surface = shadow_draw_target.snapshot();
let shadow_origin = box_bounds.origin - shadow_draw_target_offset;
let transform = self.draw_target.get_transform();
let shadow_origin = transform.transform_point(&shadow_origin.to_azure_point());
let shadow_offset = matrix.transform_point(&offset.to_azure_point());
self.push_clip_outside_rect(box_bounds);
self.draw_target.draw_surface_with_shadow(shadow_surface,
&shadow_origin,
&color,
&shadow_offset,
sigma,
OverOp);
self.draw_target.pop_clip();
}

fn push_clip_outside_rect(&self, bounds: &Rect<Au>) {
// +-----------+
// |2 |1
// | |
// | +---+---+
// | |9 |6 |5, 10
// | | | |
// | +---+ |
// | 8 7 |
// | |
// +-----------+
// 3 4

let bounds = bounds.to_azure_rect();
let tile_rect = Rect(Point2D(Au(0), Au(0)), Size2D(MAX_AU, MAX_AU)).to_azure_rect();

let path_builder = self.draw_target.create_path_builder();
path_builder.move_to(Point2D(tile_rect.max_x(), tile_rect.origin.y)); // 1
path_builder.line_to(Point2D(tile_rect.origin.x, tile_rect.origin.y)); // 2
path_builder.line_to(Point2D(tile_rect.origin.x, tile_rect.max_y())); // 3
path_builder.line_to(Point2D(tile_rect.max_x(), tile_rect.max_y())); // 4
path_builder.line_to(Point2D(tile_rect.max_x(), bounds.origin.y)); // 5
path_builder.line_to(Point2D(bounds.max_x(), bounds.origin.y)); // 6
path_builder.line_to(Point2D(bounds.max_x(), bounds.max_y())); // 7
path_builder.line_to(Point2D(bounds.origin.x, bounds.max_y())); // 8
path_builder.line_to(bounds.origin); // 9
path_builder.line_to(Point2D(tile_rect.max_x(), tile_rect.origin.y)); // 10
self.draw_target.push_clip(&path_builder.finish());
}
}

pub trait ToAzurePoint {
@@ -773,6 +860,22 @@ 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<i32> {
fn to_azure_int_size(&self) -> Size2D<i32> {
Size2D(self.width, self.height)
}
}

trait ToSideOffsetsPx {
fn to_float_px(&self) -> SideOffsets2D<AzFloat>;
}
@@ -510,6 +510,7 @@ impl WorkerThread {
page_rect: tile.page_rect,
screen_rect: tile.screen_rect,
transient_clip_rect: None,
scale: scale,
};

// Apply the translation to paint the tile we want.
@@ -24,12 +24,15 @@ 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, PseudoDisplayItemClass, SidewaysLeft, SidewaysRight};
use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, StackingContext};
use gfx::display_list::{TextDisplayItem, TextDisplayItemClass, Upright};
use gfx::display_list::{BOX_SHADOW_INFLATION_FACTOR, BackgroundAndBorderLevel, BaseDisplayItem};
use gfx::display_list::{BorderDisplayItem, BorderDisplayItemClass, BoxShadowDisplayItem};
use gfx::display_list::{BoxShadowDisplayItemClass, ContentStackingLevel, DisplayList};
use gfx::display_list::{FloatStackingLevel, GradientDisplayItem, GradientDisplayItemClass};
use gfx::display_list::{GradientStop, ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem};
use gfx::display_list::{LineDisplayItemClass, PositionedDescendantStackingLevel};
use gfx::display_list::{PseudoDisplayItemClass, RootOfStackingContextLevel, SidewaysLeft};
use gfx::display_list::{SidewaysRight, SolidColorDisplayItem, SolidColorDisplayItemClass};
use gfx::display_list::{StackingLevel, TextDisplayItem, TextDisplayItemClass, Upright};
use gfx::paint_task::PaintLayer;
use servo_msg::compositor_msg::{FixedPosition, Scrollable};
use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg};
@@ -112,6 +115,16 @@ pub trait FragmentDisplayListBuilding {
level: StackingLevel,
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>,
@@ -407,6 +420,31 @@ 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, level, *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,
}));
}
}

fn build_display_list_for_borders_if_applicable(&self,
style: &ComputedValues,
display_list: &mut DisplayList,
@@ -556,6 +594,34 @@ impl FragmentDisplayListBuilding for Fragment {
*clip_rect);
display_list.push(PseudoDisplayItemClass(base_display_item), 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) => {
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.