aboutsummaryrefslogtreecommitdiffstats
path: root/components/gfx
diff options
context:
space:
mode:
authorPatrick Walton <pcwalton@mimiga.net>2014-12-18 12:57:58 -0800
committerPatrick Walton <pcwalton@mimiga.net>2015-03-02 13:28:51 -0800
commit09358b908d937a3dfbb74a5bdcc083dbf2b1df1c (patch)
tree626201327ab456f51b283a95f3319b11ec907dd7 /components/gfx
parentfed878710c5697b49ccf5185ebe08a58be27073f (diff)
downloadservo-09358b908d937a3dfbb74a5bdcc083dbf2b1df1c.tar.gz
servo-09358b908d937a3dfbb74a5bdcc083dbf2b1df1c.zip
layout: Implement `text-shadow` per CSS-TEXT-DECORATION-3 § 4.
Diffstat (limited to 'components/gfx')
-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
3 files changed, 223 insertions, 119 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,