aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authorbors-servo <metajack+bors@gmail.com>2015-03-02 14:54:52 -0700
committerbors-servo <metajack+bors@gmail.com>2015-03-02 14:54:52 -0700
commit93d1f40a96df69eb9d38890df96c621e180d78cc (patch)
treedab39945bbadcbf717af71ce01a6f3b9eafa49a2 /components
parent9eaa48b793de78b713e6c3a3c79c4060084d5fbe (diff)
parent09358b908d937a3dfbb74a5bdcc083dbf2b1df1c (diff)
downloadservo-93d1f40a96df69eb9d38890df96c621e180d78cc.tar.gz
servo-93d1f40a96df69eb9d38890df96c621e180d78cc.zip
auto merge of #4475 : pcwalton/servo/text-shadow, r=mbrubeck
r? @mbrubeck Depends on servo/rust-geom#64.
Diffstat (limited to 'components')
-rw-r--r--components/gfx/display_list/mod.rs39
-rw-r--r--components/gfx/paint_context.rs301
-rw-r--r--components/gfx/paint_task.rs2
-rw-r--r--components/layout/display_list_builder.rs117
-rw-r--r--components/layout/fragment.rs6
-rw-r--r--components/script/dom/webidls/CSSStyleDeclaration.webidl1
-rw-r--r--components/servo/Cargo.lock2
-rw-r--r--components/style/properties.mako.rs165
8 files changed, 482 insertions, 151 deletions
diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs
index 694fdeef125..7ad745a1ae9 100644
--- a/components/gfx/display_list/mod.rs
+++ b/components/gfx/display_list/mod.rs
@@ -50,10 +50,9 @@ 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;
+/// The factor that we multiply the blur radius by in order to inflate the boundaries of display
+/// items that involve a blur. This ensures that the display item boundaries include all the ink.
+pub static BLUR_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.
@@ -248,8 +247,8 @@ impl StackingContext {
{
let mut paint_subcontext = PaintContext {
draw_target: temporary_draw_target.clone(),
- font_ctx: &mut *paint_context.font_ctx,
- page_rect: paint_context.page_rect,
+ font_context: &mut *paint_context.font_context,
+ page_rect: *tile_bounds,
screen_rect: paint_context.screen_rect,
clip_rect: clip_rect.map(|clip_rect| *clip_rect),
transient_clip: None,
@@ -714,7 +713,10 @@ impl DisplayItemMetadata {
/// Paints a solid color.
#[derive(Clone)]
pub struct SolidColorDisplayItem {
+ /// Fields common to all display items.
pub base: BaseDisplayItem,
+
+ /// The color.
pub color: Color,
}
@@ -733,8 +735,14 @@ pub struct TextDisplayItem {
/// The color of the text.
pub text_color: Color,
+ /// The position of the start of the baseline of this text.
pub baseline_origin: Point2D<Au>,
+
+ /// The orientation of the text: upright or sideways left/right.
pub orientation: TextOrientation,
+
+ /// The blur radius for this text. If zero, this text is not blurred.
+ pub blur_radius: Au,
}
#[derive(Clone, Eq, PartialEq)]
@@ -858,8 +866,21 @@ pub struct BoxShadowDisplayItem {
/// The spread radius of this shadow.
pub spread_radius: Au,
- /// True if this shadow is inset; false if it's outset.
- pub inset: bool,
+ /// How we should clip the result.
+ pub clip_mode: BoxShadowClipMode,
+}
+
+/// How a box shadow should be clipped.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum BoxShadowClipMode {
+ /// No special clipping should occur. This is used for (shadowed) text decorations.
+ None,
+ /// The area inside `box_bounds` should be clipped out. Corresponds to the normal CSS
+ /// `box-shadow`.
+ Outset,
+ /// The area outside `box_bounds` should be clipped out. Corresponds to the `inset` flag on CSS
+ /// `box-shadow`.
+ Inset,
}
pub enum DisplayItemIterator<'a> {
@@ -947,7 +968,7 @@ impl DisplayItem {
box_shadow.color,
box_shadow.blur_radius,
box_shadow.spread_radius,
- box_shadow.inset)
+ box_shadow.clip_mode)
}
}
}
diff --git a/components/gfx/paint_context.rs b/components/gfx/paint_context.rs
index d831a01d7ec..82184bf06de 100644
--- a/components/gfx/paint_context.rs
+++ b/components/gfx/paint_context.rs
@@ -4,45 +4,47 @@
//! Painting of display lists using Moz2D/Azure.
+use color;
+use display_list::TextOrientation::{SidewaysLeft, SidewaysRight, Upright};
+use display_list::{BLUR_INFLATION_FACTOR, BorderRadii, BoxShadowClipMode, ClippingRegion};
+use display_list::{TextDisplayItem};
+use filters;
+use font_context::FontContext;
+use text::TextRun;
+use text::glyph::CharIndex;
+
use azure::azure::AzIntSize;
use azure::azure_hl::{Color, ColorPattern};
use azure::azure_hl::{DrawOptions, DrawSurfaceOptions, DrawTarget, ExtendMode, FilterType};
-use azure::azure_hl::{GaussianBlurInput, GradientStop, Filter, LinearGradientPattern};
-use azure::azure_hl::{PatternRef, Path, PathBuilder, CompositionOp};
use azure::azure_hl::{GaussianBlurAttribute, StrokeOptions, SurfaceFormat};
+use azure::azure_hl::{GaussianBlurInput, GradientStop, Filter, FilterNode, LinearGradientPattern};
use azure::azure_hl::{JoinStyle, CapStyle};
+use azure::azure_hl::{PatternRef, Path, PathBuilder, CompositionOp};
use azure::scaled_font::ScaledFont;
use azure::{AzFloat, struct__AzDrawOptions, struct__AzGlyph};
use azure::{struct__AzGlyphBuffer, struct__AzPoint, AzDrawTargetFillGlyphs};
-use color;
-use display_list::TextOrientation::{SidewaysLeft, SidewaysRight, Upright};
-use display_list::{BOX_SHADOW_INFLATION_FACTOR, BorderRadii, ClippingRegion, TextDisplayItem};
-use filters;
-use font_context::FontContext;
use geom::matrix2d::Matrix2D;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::side_offsets::SideOffsets2D;
use geom::size::Size2D;
use libc::types::common::c99::{uint16_t, uint32_t};
-use png::PixelsByColorType;
use net::image::base::Image;
-use util::geometry::{Au, MAX_RECT};
-use util::opts;
-use util::range::Range;
+use png::PixelsByColorType;
use std::default::Default;
use std::f32;
use std::mem;
use std::num::Float;
use std::ptr;
-use style::computed_values::{border_style, filter, mix_blend_mode};
use std::sync::Arc;
-use text::TextRun;
-use text::glyph::CharIndex;
+use style::computed_values::{border_style, filter, mix_blend_mode};
+use util::geometry::{self, Au, MAX_RECT, ZERO_RECT};
+use util::opts;
+use util::range::Range;
pub struct PaintContext<'a> {
pub draw_target: DrawTarget,
- pub font_ctx: &'a mut Box<FontContext>,
+ pub font_context: &'a mut Box<FontContext>,
/// The rectangle that this context encompasses in page coordinates.
pub page_rect: Rect<f32>,
/// The rectangle that this context encompasses in screen coordinates (pixels).
@@ -803,8 +805,9 @@ impl<'a> PaintContext<'a> {
self.draw_border_path(&original_bounds, direction, border, radius, scaled_color);
}
+ /// Draws the given text display item into the current context.
pub fn draw_text(&mut self, text: &TextDisplayItem) {
- let current_transform = self.draw_target.get_transform();
+ let draw_target_transform = self.draw_target.get_transform();
// Optimization: Don’t set a transform matrix for upright text, and pass a start point to
// `draw_text_into_context`.
@@ -816,35 +819,41 @@ impl<'a> PaintContext<'a> {
SidewaysLeft => {
let x = text.baseline_origin.x.to_subpx() as AzFloat;
let y = text.baseline_origin.y.to_subpx() as AzFloat;
- self.draw_target.set_transform(&current_transform.mul(&Matrix2D::new(0., -1.,
- 1., 0.,
- x, y)));
+ self.draw_target.set_transform(&draw_target_transform.mul(&Matrix2D::new(0., -1.,
+ 1., 0.,
+ x, y)));
Point2D::zero()
}
SidewaysRight => {
let x = text.baseline_origin.x.to_subpx() as AzFloat;
let y = text.baseline_origin.y.to_subpx() as AzFloat;
- self.draw_target.set_transform(&current_transform.mul(&Matrix2D::new(0., 1.,
- -1., 0.,
- x, y)));
+ self.draw_target.set_transform(&draw_target_transform.mul(&Matrix2D::new(0., 1.,
+ -1., 0.,
+ x, y)));
Point2D::zero()
}
};
- self.font_ctx
+ // Draw the text.
+ let temporary_draw_target =
+ self.create_draw_target_for_blur_if_necessary(&text.base.bounds, text.blur_radius);
+ self.font_context
.get_paint_font_from_template(&text.text_run.font_template,
text.text_run.actual_pt_size)
.borrow()
- .draw_text_into_context(self,
- &*text.text_run,
- &text.range,
- baseline_origin,
- text.text_color,
- opts::get().enable_text_antialiasing);
+ .draw_text(&temporary_draw_target.draw_target,
+ &*text.text_run,
+ &text.range,
+ baseline_origin,
+ text.text_color,
+ opts::get().enable_text_antialiasing);
+
+ // Blur, if necessary.
+ self.blur_if_necessary(temporary_draw_target, text.blur_radius);
// Undo the transform, only when we did one.
if text.orientation != Upright {
- self.draw_target.set_transform(&current_transform)
+ self.draw_target.set_transform(&draw_target_transform)
}
}
@@ -930,80 +939,85 @@ impl<'a> PaintContext<'a> {
color: Color,
blur_radius: Au,
spread_radius: Au,
- inset: bool) {
+ clip_mode: BoxShadowClipMode) {
// 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();
- }
-
+ // If we have blur, create a new draw target.
let shadow_bounds = box_bounds.translate(offset).inflate(spread_radius, spread_radius);
+ let side_inflation = blur_radius * BLUR_INFLATION_FACTOR;
+ let inflated_shadow_bounds = shadow_bounds.inflate(side_inflation, side_inflation);
+ let temporary_draw_target =
+ self.create_draw_target_for_blur_if_necessary(&inflated_shadow_bounds, blur_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))
+ match clip_mode {
+ BoxShadowClipMode::Inset => {
+ path = temporary_draw_target.draw_target
+ .create_rectangular_border_path(&MAX_RECT,
+ &shadow_bounds);
+ self.draw_target.push_clip(&self.draw_target.create_rectangular_path(box_bounds))
+ }
+ BoxShadowClipMode::Outset => {
+ path = temporary_draw_target.draw_target.create_rectangular_path(&shadow_bounds);
+ self.draw_target.push_clip(&self.draw_target
+ .create_rectangular_border_path(&MAX_RECT,
+ box_bounds))
+ }
+ BoxShadowClipMode::None => {
+ path = temporary_draw_target.draw_target.create_rectangular_path(&shadow_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(FilterType::GaussianBlur);
- blur_filter.set_attribute(GaussianBlurAttribute::StdDeviation(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);
- }
+ // Draw the shadow, and blur if we need to.
+ temporary_draw_target.draw_target.fill(&path,
+ &ColorPattern::new(color),
+ &DrawOptions::new(1.0, 0));
+ self.blur_if_necessary(temporary_draw_target, blur_radius);
- // Undo the draw target's clip.
- self.draw_target.pop_clip();
+ // Undo the draw target's clip if we need to, and push back the stacking context clip.
+ if clip_mode != BoxShadowClipMode::None {
+ self.draw_target.pop_clip()
+ }
- // Push back the stacking context clip.
self.push_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.)
+ fn create_draw_target_for_blur_if_necessary(&self, box_bounds: &Rect<Au>, blur_radius: Au)
+ -> TemporaryDrawTarget {
+ if blur_radius == Au(0) {
+ return TemporaryDrawTarget::from_main_draw_target(&self.draw_target)
+ }
+
+ // Intersect display item bounds with the tile bounds inflated by blur radius to get the
+ // smallest possible rectangle that encompasses all the paint.
+ let side_inflation = blur_radius * BLUR_INFLATION_FACTOR;
+ let tile_box_bounds =
+ geometry::f32_rect_to_au_rect(self.page_rect).intersection(box_bounds)
+ .unwrap_or(ZERO_RECT)
+ .inflate(side_inflation, side_inflation);
+ TemporaryDrawTarget::from_bounds(&self.draw_target, &tile_box_bounds)
+ }
+
+ /// Performs a blur using the draw target created in
+ /// `create_draw_target_for_blur_if_necessary`.
+ fn blur_if_necessary(&self, temporary_draw_target: TemporaryDrawTarget, blur_radius: Au) {
+ if blur_radius == Au(0) {
+ return
+ }
+
+ let blur_filter = self.draw_target.create_filter(FilterType::GaussianBlur);
+ blur_filter.set_attribute(GaussianBlurAttribute::StdDeviation(blur_radius.to_subpx() as
+ AzFloat));
+ blur_filter.set_input(GaussianBlurInput, &temporary_draw_target.draw_target.snapshot());
+ temporary_draw_target.draw_filter(&self.draw_target, blur_filter);
+ }
+
pub fn push_clip_if_applicable(&self) {
if let Some(ref clip_rect) = self.clip_rect {
self.draw_push_clip(clip_rect)
@@ -1042,23 +1056,31 @@ impl<'a> PaintContext<'a> {
pub trait ToAzurePoint {
fn to_azure_point(&self) -> Point2D<AzFloat>;
+ fn to_subpx_azure_point(&self) -> Point2D<AzFloat>;
}
impl ToAzurePoint for Point2D<Au> {
fn to_azure_point(&self) -> Point2D<AzFloat> {
Point2D(self.x.to_nearest_px() as AzFloat, self.y.to_nearest_px() as AzFloat)
}
+ fn to_subpx_azure_point(&self) -> Point2D<AzFloat> {
+ Point2D(self.x.to_subpx() as AzFloat, self.y.to_subpx() as AzFloat)
+ }
}
pub trait ToAzureRect {
fn to_azure_rect(&self) -> Rect<AzFloat>;
+ fn to_subpx_azure_rect(&self) -> Rect<AzFloat>;
}
impl ToAzureRect for Rect<Au> {
fn to_azure_rect(&self) -> Rect<AzFloat> {
- Rect(self.origin.to_azure_point(),
- Size2D(self.size.width.to_nearest_px() as AzFloat,
- self.size.height.to_nearest_px() as AzFloat))
+ Rect(self.origin.to_azure_point(), Size2D(self.size.width.to_nearest_px() as AzFloat,
+ self.size.height.to_nearest_px() as AzFloat))
+ }
+ fn to_subpx_azure_rect(&self) -> Rect<AzFloat> {
+ Rect(self.origin.to_subpx_azure_point(), Size2D(self.size.width.to_subpx() as AzFloat,
+ self.size.height.to_subpx() as AzFloat))
}
}
@@ -1127,24 +1149,23 @@ impl ToRadiiPx for BorderRadii<Au> {
}
trait ScaledFontExtensionMethods {
- fn draw_text_into_context(&self,
- rctx: &PaintContext,
- run: &Box<TextRun>,
- range: &Range<CharIndex>,
- baseline_origin: Point2D<Au>,
- color: Color,
- antialias: bool);
+ fn draw_text(&self,
+ draw_target: &DrawTarget,
+ run: &Box<TextRun>,
+ range: &Range<CharIndex>,
+ baseline_origin: Point2D<Au>,
+ color: Color,
+ antialias: bool);
}
impl ScaledFontExtensionMethods for ScaledFont {
- fn draw_text_into_context(&self,
- rctx: &PaintContext,
- run: &Box<TextRun>,
- range: &Range<CharIndex>,
- baseline_origin: Point2D<Au>,
- color: Color,
- antialias: bool) {
- let target = rctx.get_draw_target();
+ fn draw_text(&self,
+ draw_target: &DrawTarget,
+ run: &Box<TextRun>,
+ range: &Range<CharIndex>,
+ baseline_origin: Point2D<Au>,
+ color: Color,
+ antialias: bool) {
let pattern = ColorPattern::new(color);
let azure_pattern = pattern.azure_color_pattern;
assert!(!azure_pattern.is_null());
@@ -1190,7 +1211,7 @@ impl ScaledFontExtensionMethods for ScaledFont {
unsafe {
// TODO(Issue #64): this call needs to move into azure_hl.rs
- AzDrawTargetFillGlyphs(target.azure_draw_target,
+ AzDrawTargetFillGlyphs(draw_target.azure_draw_target,
self.get_ref(),
&mut glyphbuf,
azure_pattern,
@@ -1290,3 +1311,65 @@ impl ToAzureCompositionOp for mix_blend_mode::T {
}
}
+/// Represents a temporary drawing surface. Some operations that perform complex compositing
+/// operations need this.
+struct TemporaryDrawTarget {
+ /// The draw target.
+ draw_target: DrawTarget,
+ /// The distance from the top left of the main draw target to the top left of this temporary
+ /// draw target.
+ offset: Point2D<AzFloat>,
+}
+
+impl TemporaryDrawTarget {
+ /// Creates a temporary draw target that simply draws to the main draw target.
+ fn from_main_draw_target(main_draw_target: &DrawTarget) -> TemporaryDrawTarget {
+ TemporaryDrawTarget {
+ draw_target: main_draw_target.clone(),
+ offset: Point2D(0.0, 0.0),
+ }
+ }
+
+ /// Creates a temporary draw target large enough to encompass the given bounding rect in page
+ /// coordinates. The temporary draw target will have the same transform as the tile we're
+ /// drawing to.
+ fn from_bounds(main_draw_target: &DrawTarget, bounds: &Rect<Au>) -> TemporaryDrawTarget {
+ let draw_target_transform = main_draw_target.get_transform();
+ let temporary_draw_target_bounds =
+ draw_target_transform.transform_rect(&bounds.to_subpx_azure_rect());
+ let temporary_draw_target_size =
+ Size2D(temporary_draw_target_bounds.size.width.ceil() as i32,
+ temporary_draw_target_bounds.size.height.ceil() as i32);
+ let temporary_draw_target =
+ main_draw_target.create_similar_draw_target(&temporary_draw_target_size,
+ main_draw_target.get_format());
+ let matrix =
+ Matrix2D::identity().translate(-temporary_draw_target_bounds.origin.x as AzFloat,
+ -temporary_draw_target_bounds.origin.y as AzFloat)
+ .mul(&draw_target_transform);
+ temporary_draw_target.set_transform(&matrix);
+ TemporaryDrawTarget {
+ draw_target: temporary_draw_target,
+ offset: temporary_draw_target_bounds.origin,
+ }
+ }
+
+ /// Composites this temporary draw target onto the main surface, with the given Azure filter.
+ fn draw_filter(self, main_draw_target: &DrawTarget, filter: FilterNode) {
+ let main_draw_target_transform = main_draw_target.get_transform();
+ let temporary_draw_target_size = self.draw_target.get_size();
+ let temporary_draw_target_size = Size2D(temporary_draw_target_size.width as AzFloat,
+ temporary_draw_target_size.height as AzFloat);
+
+ // Blit the blur onto the tile. We undo the transforms here because we want to directly
+ // stack the temporary draw target onto the tile.
+ main_draw_target.set_transform(&Matrix2D::identity());
+ main_draw_target.draw_filter(&filter,
+ &Rect(Point2D(0.0, 0.0), temporary_draw_target_size),
+ &self.offset,
+ DrawOptions::new(1.0, 0));
+ main_draw_target.set_transform(&main_draw_target_transform);
+
+ }
+}
+
diff --git a/components/gfx/paint_task.rs b/components/gfx/paint_task.rs
index 5c9008bb009..731842d3f89 100644
--- a/components/gfx/paint_task.rs
+++ b/components/gfx/paint_task.rs
@@ -536,7 +536,7 @@ impl WorkerThread {
// Build the paint context.
let mut paint_context = PaintContext {
draw_target: draw_target.clone(),
- font_ctx: &mut self.font_context,
+ font_context: &mut self.font_context,
page_rect: tile.page_rect,
screen_rect: tile.screen_rect,
clip_rect: None,
diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs
index b41c407eb58..25a292e2780 100644
--- a/components/layout/display_list_builder.rs
+++ b/components/layout/display_list_builder.rs
@@ -24,8 +24,8 @@ use util::{OpaqueNodeMethods, ToGfxColor};
use geom::approxeq::ApproxEq;
use geom::{Point2D, Rect, Size2D, SideOffsets2D};
use gfx::color;
-use gfx::display_list::{BOX_SHADOW_INFLATION_FACTOR, BaseDisplayItem, BorderDisplayItem};
-use gfx::display_list::{BorderRadii, BoxShadowDisplayItem, ClippingRegion};
+use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDisplayItem};
+use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion};
use gfx::display_list::{DisplayItem, DisplayList, DisplayItemMetadata};
use gfx::display_list::{GradientDisplayItem};
use gfx::display_list::{GradientStop, ImageDisplayItem, LineDisplayItem};
@@ -39,7 +39,7 @@ use msg::constellation_msg::Msg as ConstellationMsg;
use msg::constellation_msg::ConstellationChan;
use net::image::holder::ImageHolder;
use servo_util::cursor::Cursor;
-use servo_util::geometry::{self, Au, to_px, to_frac_px};
+use servo_util::geometry::{self, Au, ZERO_POINT, to_px, to_frac_px};
use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
use servo_util::opts;
use std::default::Default;
@@ -196,12 +196,17 @@ pub trait FragmentDisplayListBuilding {
stacking_relative_border_box: &Rect<Au>)
-> ClippingRegion;
- /// Creates the text display item for one text fragment.
+ /// Creates the text display item for one text fragment. This can be called multiple times for
+ /// one fragment if there are text shadows.
+ ///
+ /// `shadow_blur_radius` will be `Some` if this is a shadow, even if the blur radius is zero.
fn build_display_list_for_text_fragment(&self,
display_list: &mut DisplayList,
text_fragment: &ScannedTextFragmentInfo,
text_color: RGBA,
stacking_relative_content_box: &Rect<Au>,
+ shadow_blur_radius: Option<Au>,
+ offset: &Point2D<Au>,
clip: &ClippingRegion);
/// Creates the display item for a text decoration: underline, overline, or line-through.
@@ -209,7 +214,8 @@ pub trait FragmentDisplayListBuilding {
display_list: &mut DisplayList,
color: &RGBA,
stacking_relative_box: &LogicalRect<Au>,
- clip: &ClippingRegion);
+ clip: &ClippingRegion,
+ blur_radius: Au);
/// A helper method that `build_display_list` calls to create per-fragment-type display items.
fn build_fragment_type_specific_display_items(&mut self,
@@ -535,11 +541,10 @@ impl FragmentDisplayListBuilding for Fragment {
clip: &ClippingRegion) {
// 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);
+ let bounds = shadow_bounds(&absolute_bounds.translate(&Point2D(box_shadow.offset_x,
+ box_shadow.offset_y)),
+ box_shadow.blur_radius,
+ box_shadow.spread_radius);
list.push(DisplayItem::BoxShadowClass(box BoxShadowDisplayItem {
base: BaseDisplayItem::new(bounds,
DisplayItemMetadata::new(self.node,
@@ -551,7 +556,11 @@ impl FragmentDisplayListBuilding for Fragment {
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,
+ clip_mode: if box_shadow.inset {
+ BoxShadowClipMode::Inset
+ } else {
+ BoxShadowClipMode::Outset
+ },
}), level);
}
}
@@ -841,19 +850,31 @@ impl FragmentDisplayListBuilding for Fragment {
self.stacking_relative_content_box(stacking_relative_border_box);
match self.specific {
- SpecificFragmentInfo::UnscannedText(_) => {
- panic!("Shouldn't see unscanned fragments here.")
- }
- SpecificFragmentInfo::TableColumn(_) => {
- panic!("Shouldn't see table column fragments here.")
- }
SpecificFragmentInfo::ScannedText(ref text_fragment) => {
- // Create the main text display item.
+ // Create items for shadows.
+ //
+ // NB: According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front
+ // to back).
let text_color = self.style().get_color().color;
+ for text_shadow in self.style.get_effects().text_shadow.0.iter().rev() {
+ let offset = &Point2D(text_shadow.offset_x, text_shadow.offset_y);
+ let color = self.style().resolve_color(text_shadow.color);
+ self.build_display_list_for_text_fragment(display_list,
+ &**text_fragment,
+ color,
+ &stacking_relative_content_box,
+ Some(text_shadow.blur_radius),
+ offset,
+ clip);
+ }
+
+ // Create the main text display item.
self.build_display_list_for_text_fragment(display_list,
&**text_fragment,
text_color,
&stacking_relative_content_box,
+ None,
+ &Point2D(Au(0), Au(0)),
clip);
if opts::get().show_debug_fragment_borders {
@@ -932,6 +953,12 @@ impl FragmentDisplayListBuilding for Fragment {
display_list.content.push_back(DisplayItem::ImageClass(canvas_display_item));
}
+ SpecificFragmentInfo::UnscannedText(_) => {
+ panic!("Shouldn't see unscanned fragments here.")
+ }
+ SpecificFragmentInfo::TableColumn(_) => {
+ panic!("Shouldn't see table column fragments here.")
+ }
}
}
@@ -984,6 +1011,8 @@ impl FragmentDisplayListBuilding for Fragment {
text_fragment: &ScannedTextFragmentInfo,
text_color: RGBA,
stacking_relative_content_box: &Rect<Au>,
+ shadow_blur_radius: Option<Au>,
+ offset: &Point2D<Au>,
clip: &ClippingRegion) {
// Determine the orientation and cursor to use.
let (orientation, cursor) = if self.style.writing_mode.is_vertical() {
@@ -1001,6 +1030,7 @@ impl FragmentDisplayListBuilding for Fragment {
// FIXME(pcwalton): Get the real container size.
let container_size = Size2D::zero();
let metrics = &text_fragment.run.font_metrics;
+ let stacking_relative_content_box = stacking_relative_content_box.translate(offset);
let baseline_origin = stacking_relative_content_box.origin +
LogicalPoint::new(self.style.writing_mode,
Au(0),
@@ -1009,7 +1039,7 @@ impl FragmentDisplayListBuilding for Fragment {
// Create the text display item.
display_list.content.push_back(DisplayItem::TextClass(box TextDisplayItem {
- base: BaseDisplayItem::new(*stacking_relative_content_box,
+ base: BaseDisplayItem::new(stacking_relative_content_box,
DisplayItemMetadata::new(self.node, self.style(), cursor),
(*clip).clone()),
text_run: text_fragment.run.clone(),
@@ -1017,13 +1047,23 @@ impl FragmentDisplayListBuilding for Fragment {
text_color: text_color.to_gfx_color(),
orientation: orientation,
baseline_origin: baseline_origin,
+ blur_radius: shadow_blur_radius.unwrap_or(Au(0)),
}));
// Create display items for text decorations.
- let text_decorations = self.style().get_inheritedtext()._servo_text_decorations_in_effect;
+ let mut text_decorations = self.style()
+ .get_inheritedtext()
+ ._servo_text_decorations_in_effect;
+ if shadow_blur_radius.is_some() {
+ // If we're painting a shadow, paint the decorations the same color as the shadow.
+ text_decorations.underline = text_decorations.underline.map(|_| text_color);
+ text_decorations.overline = text_decorations.overline.map(|_| text_color);
+ text_decorations.line_through = text_decorations.line_through.map(|_| text_color);
+ }
+
let stacking_relative_content_box =
LogicalRect::from_physical(self.style.writing_mode,
- *stacking_relative_content_box,
+ stacking_relative_content_box,
container_size);
if let Some(ref underline_color) = text_decorations.underline {
let mut stacking_relative_box = stacking_relative_content_box;
@@ -1033,7 +1073,8 @@ impl FragmentDisplayListBuilding for Fragment {
self.build_display_list_for_text_decoration(display_list,
underline_color,
&stacking_relative_box,
- clip)
+ clip,
+ shadow_blur_radius.unwrap_or(Au(0)))
}
if let Some(ref overline_color) = text_decorations.overline {
@@ -1042,7 +1083,8 @@ impl FragmentDisplayListBuilding for Fragment {
self.build_display_list_for_text_decoration(display_list,
overline_color,
&stacking_relative_box,
- clip)
+ clip,
+ shadow_blur_radius.unwrap_or(Au(0)))
}
if let Some(ref line_through_color) = text_decorations.line_through {
@@ -1053,7 +1095,8 @@ impl FragmentDisplayListBuilding for Fragment {
self.build_display_list_for_text_decoration(display_list,
line_through_color,
&stacking_relative_box,
- clip)
+ clip,
+ shadow_blur_radius.unwrap_or(Au(0)))
}
}
@@ -1061,16 +1104,27 @@ impl FragmentDisplayListBuilding for Fragment {
display_list: &mut DisplayList,
color: &RGBA,
stacking_relative_box: &LogicalRect<Au>,
- clip: &ClippingRegion) {
+ clip: &ClippingRegion,
+ blur_radius: Au) {
+ // Perhaps surprisingly, text decorations are box shadows. This is because they may need
+ // to have blur in the case of `text-shadow`, and this doesn't hurt performance because box
+ // shadows are optimized into essentially solid colors if there is no need for the blur.
+ //
// FIXME(pcwalton, #2795): Get the real container size.
let container_size = Size2D::zero();
let stacking_relative_box = stacking_relative_box.to_physical(self.style.writing_mode,
container_size);
-
let metadata = DisplayItemMetadata::new(self.node, &*self.style, Cursor::DefaultCursor);
- display_list.content.push_back(DisplayItem::SolidColorClass(box SolidColorDisplayItem {
- base: BaseDisplayItem::new(stacking_relative_box, metadata, (*clip).clone()),
+ display_list.content.push_back(DisplayItem::BoxShadowClass(box BoxShadowDisplayItem {
+ base: BaseDisplayItem::new(shadow_bounds(&stacking_relative_box, blur_radius, Au(0)),
+ metadata,
+ (*clip).clone()),
+ box_bounds: stacking_relative_box,
color: color.to_gfx_color(),
+ offset: ZERO_POINT,
+ blur_radius: blur_radius,
+ spread_radius: Au(0),
+ clip_mode: BoxShadowClipMode::None,
}))
}
}
@@ -1423,3 +1477,10 @@ impl StackingContextConstruction for DisplayList {
}
}
+/// Adjusts `content_rect` as necessary for the given spread, and blur so that the resulting
+/// bounding rect contains all of a shadow's ink.
+fn shadow_bounds(content_rect: &Rect<Au>, blur_radius: Au, spread_radius: Au) -> Rect<Au> {
+ let inflation = spread_radius + blur_radius * BLUR_INFLATION_FACTOR;
+ content_rect.inflate(inflation, inflation)
+}
+
diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs
index 89b6d743fd1..26a91139b31 100644
--- a/components/layout/fragment.rs
+++ b/components/layout/fragment.rs
@@ -25,7 +25,7 @@ use wrapper::{TLayoutNode, ThreadSafeLayoutNode};
use geom::num::Zero;
use geom::{Point2D, Rect, Size2D};
-use gfx::display_list::{BOX_SHADOW_INFLATION_FACTOR, OpaqueNode};
+use gfx::display_list::{BLUR_INFLATION_FACTOR, OpaqueNode};
use gfx::text::glyph::CharIndex;
use gfx::text::text_run::{TextRun, TextRunSlice};
use script_traits::UntrustedNodeAddress;
@@ -2043,8 +2043,8 @@ impl Fragment {
// Box shadows cause us to draw outside our border box.
for box_shadow in self.style().get_effects().box_shadow.iter() {
let offset = Point2D(box_shadow.offset_x, box_shadow.offset_y);
- let inflation = box_shadow.spread_radius +
- box_shadow.blur_radius * BOX_SHADOW_INFLATION_FACTOR;
+ let inflation = box_shadow.spread_radius + box_shadow.blur_radius *
+ BLUR_INFLATION_FACTOR;
overflow = overflow.union(&border_box.translate(&offset).inflate(inflation, inflation))
}
diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl
index 0d716d4ab15..1d642acf17c 100644
--- a/components/script/dom/webidls/CSSStyleDeclaration.webidl
+++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl
@@ -79,6 +79,7 @@ partial interface CSSStyleDeclaration {
[TreatNullAs=EmptyString] attribute DOMString boxSizing;
[TreatNullAs=EmptyString] attribute DOMString boxShadow;
+ [TreatNullAs=EmptyString] attribute DOMString textShadow;
//[TreatNullAs=EmptyString] attribute DOMString float; //XXXjdm need BinaryName annotation
diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock
index 941bd221322..a52a5ec3191 100644
--- a/components/servo/Cargo.lock
+++ b/components/servo/Cargo.lock
@@ -292,7 +292,7 @@ dependencies = [
[[package]]
name = "geom"
version = "0.1.0"
-source = "git+https://github.com/servo/rust-geom#6b079ba2738ed15bac2b6ec66850494afb9f2b4c"
+source = "git+https://github.com/servo/rust-geom#876c2fceee211130d1294eacdc1bd8742c52540e"
dependencies = [
"rustc-serialize 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
diff --git a/components/style/properties.mako.rs b/components/style/properties.mako.rs
index b9049a0a30c..2ff84b2107c 100644
--- a/components/style/properties.mako.rs
+++ b/components/style/properties.mako.rs
@@ -2030,6 +2030,171 @@ pub mod longhands {
}
</%self:longhand>
+ <%self:longhand name="text-shadow">
+ use cssparser::{self, ToCss};
+ use std::fmt;
+ use text_writer::{self, TextWriter};
+
+ use values::computed::{Context, ToComputedValue};
+
+ #[derive(Clone, PartialEq)]
+ pub struct SpecifiedValue(Vec<SpecifiedTextShadow>);
+
+ #[derive(Clone, PartialEq)]
+ pub struct SpecifiedTextShadow {
+ pub offset_x: specified::Length,
+ pub offset_y: specified::Length,
+ pub blur_radius: specified::Length,
+ pub color: Option<specified::CSSColor>,
+ }
+
+ impl fmt::Debug for SpecifiedTextShadow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let _ = write!(f,
+ "{:?} {:?} {:?}",
+ self.offset_x,
+ self.offset_y,
+ self.blur_radius);
+ if let Some(ref color) = self.color {
+ let _ = write!(f, "{:?}", color);
+ }
+ Ok(())
+ }
+ }
+
+ pub mod computed_value {
+ use cssparser::Color;
+ use util::geometry::Au;
+
+ #[derive(Clone, PartialEq, Debug)]
+ pub struct T(pub Vec<TextShadow>);
+
+ #[derive(Clone, PartialEq, Debug)]
+ pub struct TextShadow {
+ pub offset_x: Au,
+ pub offset_y: Au,
+ pub blur_radius: Au,
+ pub color: Color,
+ }
+ }
+
+ impl ToCss for SpecifiedValue {
+ fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
+ let mut iter = self.0.iter();
+ if let Some(shadow) = iter.next() {
+ try!(shadow.to_css(dest));
+ } else {
+ try!(dest.write_str("none"));
+ return Ok(())
+ }
+ for shadow in iter {
+ try!(dest.write_str(", "));
+ try!(shadow.to_css(dest));
+ }
+ Ok(())
+ }
+ }
+
+ impl ToCss for SpecifiedTextShadow {
+ fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
+ try!(self.offset_x.to_css(dest));
+ try!(dest.write_str(" "));
+ try!(self.offset_y.to_css(dest));
+ try!(dest.write_str(" "));
+ try!(self.blur_radius.to_css(dest));
+
+ if let Some(ref color) = self.color {
+ try!(dest.write_str(" "));
+ try!(color.to_css(dest));
+ }
+ Ok(())
+ }
+ }
+
+ #[inline]
+ pub fn get_initial_value() -> computed_value::T {
+ computed_value::T(Vec::new())
+ }
+
+ pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
+ if input.try(|input| input.expect_ident_matching("none")).is_ok() {
+ Ok(SpecifiedValue(Vec::new()))
+ } else {
+ input.parse_comma_separated(parse_one_text_shadow).map(|shadows| {
+ SpecifiedValue(shadows)
+ })
+ }
+ }
+
+ fn parse_one_text_shadow(input: &mut Parser) -> Result<SpecifiedTextShadow,()> {
+ use util::geometry::Au;
+ let mut lengths = [specified::Length::Au(Au(0)); 3];
+ let mut lengths_parsed = false;
+ let mut color = None;
+
+ loop {
+ if !lengths_parsed {
+ if let Ok(value) = input.try(specified::Length::parse) {
+ lengths[0] = value;
+ let mut length_parsed_count = 1;
+ while length_parsed_count < 3 {
+ if let Ok(value) = input.try(specified::Length::parse) {
+ lengths[length_parsed_count] = value
+ } else {
+ break
+ }
+ length_parsed_count += 1;
+ }
+
+ // The first two lengths must be specified.
+ if length_parsed_count < 2 {
+ return Err(())
+ }
+
+ lengths_parsed = true;
+ continue
+ }
+ }
+ if color.is_none() {
+ if let Ok(value) = input.try(specified::CSSColor::parse) {
+ color = Some(value);
+ continue
+ }
+ }
+ break
+ }
+
+ // Lengths must be specified.
+ if !lengths_parsed {
+ return Err(())
+ }
+
+ Ok(SpecifiedTextShadow {
+ offset_x: lengths[0],
+ offset_y: lengths[1],
+ blur_radius: lengths[2],
+ color: color,
+ })
+ }
+
+ impl ToComputedValue for SpecifiedValue {
+ type ComputedValue = computed_value::T;
+
+ fn to_computed_value(&self, context: &computed::Context) -> computed_value::T {
+ computed_value::T(self.0.iter().map(|value| {
+ computed_value::TextShadow {
+ offset_x: value.offset_x.to_computed_value(context),
+ offset_y: value.offset_y.to_computed_value(context),
+ blur_radius: value.blur_radius.to_computed_value(context),
+ color: value.color
+ .as_ref()
+ .map(|color| color.parsed)
+ .unwrap_or(cssparser::Color::CurrentColor),
+ }
+ }).collect())
+ }
+ }
+ </%self:longhand>
<%self:longhand name="filter">
use values::specified::Angle;
pub use self::computed_value::T as SpecifiedValue;