зеркало из https://github.com/mozilla/gecko-dev.git
servo: Merge #4475 - layout: Implement `text-shadow` per CSS-TEXT-DECORATION-3 § 4 (from pcwalton:text-shadow); r=mbrubeck
r? @mbrubeck Depends on servo/rust-geom#64. Source-Repo: https://github.com/servo/servo Source-Revision: 93d1f40a96df69eb9d38890df96c621e180d78cc
This commit is contained in:
Родитель
689a6b3ab5
Коммит
8d21fbc4f5
|
@ -50,10 +50,9 @@ pub use azure::azure_hl::GradientStop;
|
||||||
|
|
||||||
pub mod optimizer;
|
pub mod optimizer;
|
||||||
|
|
||||||
/// The factor that we multiply the blur radius by in order to inflate the boundaries of box shadow
|
/// The factor that we multiply the blur radius by in order to inflate the boundaries of display
|
||||||
/// display items. This ensures that the box shadow display item boundaries include all the
|
/// items that involve a blur. This ensures that the display item boundaries include all the ink.
|
||||||
/// shadow's ink.
|
pub static BLUR_INFLATION_FACTOR: i32 = 3;
|
||||||
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
|
/// 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.
|
/// compare it to another opaque handle or to another node.
|
||||||
|
@ -248,8 +247,8 @@ impl StackingContext {
|
||||||
{
|
{
|
||||||
let mut paint_subcontext = PaintContext {
|
let mut paint_subcontext = PaintContext {
|
||||||
draw_target: temporary_draw_target.clone(),
|
draw_target: temporary_draw_target.clone(),
|
||||||
font_ctx: &mut *paint_context.font_ctx,
|
font_context: &mut *paint_context.font_context,
|
||||||
page_rect: paint_context.page_rect,
|
page_rect: *tile_bounds,
|
||||||
screen_rect: paint_context.screen_rect,
|
screen_rect: paint_context.screen_rect,
|
||||||
clip_rect: clip_rect.map(|clip_rect| *clip_rect),
|
clip_rect: clip_rect.map(|clip_rect| *clip_rect),
|
||||||
transient_clip: None,
|
transient_clip: None,
|
||||||
|
@ -714,7 +713,10 @@ impl DisplayItemMetadata {
|
||||||
/// Paints a solid color.
|
/// Paints a solid color.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SolidColorDisplayItem {
|
pub struct SolidColorDisplayItem {
|
||||||
|
/// Fields common to all display items.
|
||||||
pub base: BaseDisplayItem,
|
pub base: BaseDisplayItem,
|
||||||
|
|
||||||
|
/// The color.
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -733,8 +735,14 @@ pub struct TextDisplayItem {
|
||||||
/// The color of the text.
|
/// The color of the text.
|
||||||
pub text_color: Color,
|
pub text_color: Color,
|
||||||
|
|
||||||
|
/// The position of the start of the baseline of this text.
|
||||||
pub baseline_origin: Point2D<Au>,
|
pub baseline_origin: Point2D<Au>,
|
||||||
|
|
||||||
|
/// The orientation of the text: upright or sideways left/right.
|
||||||
pub orientation: TextOrientation,
|
pub orientation: TextOrientation,
|
||||||
|
|
||||||
|
/// The blur radius for this text. If zero, this text is not blurred.
|
||||||
|
pub blur_radius: Au,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
@ -858,8 +866,21 @@ pub struct BoxShadowDisplayItem {
|
||||||
/// The spread radius of this shadow.
|
/// The spread radius of this shadow.
|
||||||
pub spread_radius: Au,
|
pub spread_radius: Au,
|
||||||
|
|
||||||
/// True if this shadow is inset; false if it's outset.
|
/// How we should clip the result.
|
||||||
pub inset: bool,
|
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> {
|
pub enum DisplayItemIterator<'a> {
|
||||||
|
@ -947,7 +968,7 @@ impl DisplayItem {
|
||||||
box_shadow.color,
|
box_shadow.color,
|
||||||
box_shadow.blur_radius,
|
box_shadow.blur_radius,
|
||||||
box_shadow.spread_radius,
|
box_shadow.spread_radius,
|
||||||
box_shadow.inset)
|
box_shadow.clip_mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,45 +4,47 @@
|
||||||
|
|
||||||
//! Painting of display lists using Moz2D/Azure.
|
//! 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::AzIntSize;
|
||||||
use azure::azure_hl::{Color, ColorPattern};
|
use azure::azure_hl::{Color, ColorPattern};
|
||||||
use azure::azure_hl::{DrawOptions, DrawSurfaceOptions, DrawTarget, ExtendMode, FilterType};
|
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::{GaussianBlurAttribute, StrokeOptions, SurfaceFormat};
|
||||||
|
use azure::azure_hl::{GaussianBlurInput, GradientStop, Filter, FilterNode, LinearGradientPattern};
|
||||||
use azure::azure_hl::{JoinStyle, CapStyle};
|
use azure::azure_hl::{JoinStyle, CapStyle};
|
||||||
|
use azure::azure_hl::{PatternRef, Path, PathBuilder, CompositionOp};
|
||||||
use azure::scaled_font::ScaledFont;
|
use azure::scaled_font::ScaledFont;
|
||||||
use azure::{AzFloat, struct__AzDrawOptions, struct__AzGlyph};
|
use azure::{AzFloat, struct__AzDrawOptions, struct__AzGlyph};
|
||||||
use azure::{struct__AzGlyphBuffer, struct__AzPoint, AzDrawTargetFillGlyphs};
|
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::matrix2d::Matrix2D;
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::side_offsets::SideOffsets2D;
|
use geom::side_offsets::SideOffsets2D;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use libc::types::common::c99::{uint16_t, uint32_t};
|
use libc::types::common::c99::{uint16_t, uint32_t};
|
||||||
use png::PixelsByColorType;
|
|
||||||
use net::image::base::Image;
|
use net::image::base::Image;
|
||||||
use util::geometry::{Au, MAX_RECT};
|
use png::PixelsByColorType;
|
||||||
use util::opts;
|
|
||||||
use util::range::Range;
|
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::f32;
|
use std::f32;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::num::Float;
|
use std::num::Float;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use style::computed_values::{border_style, filter, mix_blend_mode};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use text::TextRun;
|
use style::computed_values::{border_style, filter, mix_blend_mode};
|
||||||
use text::glyph::CharIndex;
|
use util::geometry::{self, Au, MAX_RECT, ZERO_RECT};
|
||||||
|
use util::opts;
|
||||||
|
use util::range::Range;
|
||||||
|
|
||||||
pub struct PaintContext<'a> {
|
pub struct PaintContext<'a> {
|
||||||
pub draw_target: DrawTarget,
|
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.
|
/// The rectangle that this context encompasses in page coordinates.
|
||||||
pub page_rect: Rect<f32>,
|
pub page_rect: Rect<f32>,
|
||||||
/// The rectangle that this context encompasses in screen coordinates (pixels).
|
/// 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);
|
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) {
|
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
|
// Optimization: Don’t set a transform matrix for upright text, and pass a start point to
|
||||||
// `draw_text_into_context`.
|
// `draw_text_into_context`.
|
||||||
|
@ -816,35 +819,41 @@ impl<'a> PaintContext<'a> {
|
||||||
SidewaysLeft => {
|
SidewaysLeft => {
|
||||||
let x = text.baseline_origin.x.to_subpx() as AzFloat;
|
let x = text.baseline_origin.x.to_subpx() as AzFloat;
|
||||||
let y = text.baseline_origin.y.to_subpx() as AzFloat;
|
let y = text.baseline_origin.y.to_subpx() as AzFloat;
|
||||||
self.draw_target.set_transform(¤t_transform.mul(&Matrix2D::new(0., -1.,
|
self.draw_target.set_transform(&draw_target_transform.mul(&Matrix2D::new(0., -1.,
|
||||||
1., 0.,
|
1., 0.,
|
||||||
x, y)));
|
x, y)));
|
||||||
Point2D::zero()
|
Point2D::zero()
|
||||||
}
|
}
|
||||||
SidewaysRight => {
|
SidewaysRight => {
|
||||||
let x = text.baseline_origin.x.to_subpx() as AzFloat;
|
let x = text.baseline_origin.x.to_subpx() as AzFloat;
|
||||||
let y = text.baseline_origin.y.to_subpx() as AzFloat;
|
let y = text.baseline_origin.y.to_subpx() as AzFloat;
|
||||||
self.draw_target.set_transform(¤t_transform.mul(&Matrix2D::new(0., 1.,
|
self.draw_target.set_transform(&draw_target_transform.mul(&Matrix2D::new(0., 1.,
|
||||||
-1., 0.,
|
-1., 0.,
|
||||||
x, y)));
|
x, y)));
|
||||||
Point2D::zero()
|
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,
|
.get_paint_font_from_template(&text.text_run.font_template,
|
||||||
text.text_run.actual_pt_size)
|
text.text_run.actual_pt_size)
|
||||||
.borrow()
|
.borrow()
|
||||||
.draw_text_into_context(self,
|
.draw_text(&temporary_draw_target.draw_target,
|
||||||
&*text.text_run,
|
&*text.text_run,
|
||||||
&text.range,
|
&text.range,
|
||||||
baseline_origin,
|
baseline_origin,
|
||||||
text.text_color,
|
text.text_color,
|
||||||
opts::get().enable_text_antialiasing);
|
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.
|
// Undo the transform, only when we did one.
|
||||||
if text.orientation != Upright {
|
if text.orientation != Upright {
|
||||||
self.draw_target.set_transform(¤t_transform)
|
self.draw_target.set_transform(&draw_target_transform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -930,80 +939,85 @@ impl<'a> PaintContext<'a> {
|
||||||
color: Color,
|
color: Color,
|
||||||
blur_radius: Au,
|
blur_radius: Au,
|
||||||
spread_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
|
// Remove both the transient clip and the stacking context clip, because we may need to
|
||||||
// draw outside the stacking context's clip.
|
// draw outside the stacking context's clip.
|
||||||
self.remove_transient_clip_if_applicable();
|
self.remove_transient_clip_if_applicable();
|
||||||
self.pop_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
|
// If we have blur, create a new draw target.
|
||||||
// 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 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;
|
let path;
|
||||||
if inset {
|
match clip_mode {
|
||||||
path = temporary_draw_target.create_rectangular_border_path(&MAX_RECT, &shadow_bounds);
|
BoxShadowClipMode::Inset => {
|
||||||
self.draw_target.push_clip(&self.draw_target.create_rectangular_path(box_bounds))
|
path = temporary_draw_target.draw_target
|
||||||
} else {
|
.create_rectangular_border_path(&MAX_RECT,
|
||||||
path = temporary_draw_target.create_rectangular_path(&shadow_bounds);
|
&shadow_bounds);
|
||||||
self.draw_target.push_clip(&self.draw_target
|
self.draw_target.push_clip(&self.draw_target.create_rectangular_path(box_bounds))
|
||||||
.create_rectangular_border_path(&MAX_RECT, 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));
|
// 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);
|
||||||
|
|
||||||
// Blur, if we need to.
|
// Undo the draw target's clip if we need to, and push back the stacking context clip.
|
||||||
if blur_radius > Au(0) {
|
if clip_mode != BoxShadowClipMode::None {
|
||||||
// Go ahead and create the blur now. Despite the name, Azure's notion of `StdDeviation`
|
self.draw_target.pop_clip()
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Undo the draw target's clip.
|
|
||||||
self.draw_target.pop_clip();
|
|
||||||
|
|
||||||
// Push back the stacking context clip.
|
|
||||||
self.push_clip_if_applicable();
|
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) {
|
pub fn push_clip_if_applicable(&self) {
|
||||||
if let Some(ref clip_rect) = self.clip_rect {
|
if let Some(ref clip_rect) = self.clip_rect {
|
||||||
self.draw_push_clip(clip_rect)
|
self.draw_push_clip(clip_rect)
|
||||||
|
@ -1042,23 +1056,31 @@ impl<'a> PaintContext<'a> {
|
||||||
|
|
||||||
pub trait ToAzurePoint {
|
pub trait ToAzurePoint {
|
||||||
fn to_azure_point(&self) -> Point2D<AzFloat>;
|
fn to_azure_point(&self) -> Point2D<AzFloat>;
|
||||||
|
fn to_subpx_azure_point(&self) -> Point2D<AzFloat>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToAzurePoint for Point2D<Au> {
|
impl ToAzurePoint for Point2D<Au> {
|
||||||
fn to_azure_point(&self) -> Point2D<AzFloat> {
|
fn to_azure_point(&self) -> Point2D<AzFloat> {
|
||||||
Point2D(self.x.to_nearest_px() as AzFloat, self.y.to_nearest_px() as 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 {
|
pub trait ToAzureRect {
|
||||||
fn to_azure_rect(&self) -> Rect<AzFloat>;
|
fn to_azure_rect(&self) -> Rect<AzFloat>;
|
||||||
|
fn to_subpx_azure_rect(&self) -> Rect<AzFloat>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToAzureRect for Rect<Au> {
|
impl ToAzureRect for Rect<Au> {
|
||||||
fn to_azure_rect(&self) -> Rect<AzFloat> {
|
fn to_azure_rect(&self) -> Rect<AzFloat> {
|
||||||
Rect(self.origin.to_azure_point(),
|
Rect(self.origin.to_azure_point(), Size2D(self.size.width.to_nearest_px() as AzFloat,
|
||||||
Size2D(self.size.width.to_nearest_px() as AzFloat,
|
self.size.height.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 {
|
trait ScaledFontExtensionMethods {
|
||||||
fn draw_text_into_context(&self,
|
fn draw_text(&self,
|
||||||
rctx: &PaintContext,
|
draw_target: &DrawTarget,
|
||||||
run: &Box<TextRun>,
|
run: &Box<TextRun>,
|
||||||
range: &Range<CharIndex>,
|
range: &Range<CharIndex>,
|
||||||
baseline_origin: Point2D<Au>,
|
baseline_origin: Point2D<Au>,
|
||||||
color: Color,
|
color: Color,
|
||||||
antialias: bool);
|
antialias: bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScaledFontExtensionMethods for ScaledFont {
|
impl ScaledFontExtensionMethods for ScaledFont {
|
||||||
fn draw_text_into_context(&self,
|
fn draw_text(&self,
|
||||||
rctx: &PaintContext,
|
draw_target: &DrawTarget,
|
||||||
run: &Box<TextRun>,
|
run: &Box<TextRun>,
|
||||||
range: &Range<CharIndex>,
|
range: &Range<CharIndex>,
|
||||||
baseline_origin: Point2D<Au>,
|
baseline_origin: Point2D<Au>,
|
||||||
color: Color,
|
color: Color,
|
||||||
antialias: bool) {
|
antialias: bool) {
|
||||||
let target = rctx.get_draw_target();
|
|
||||||
let pattern = ColorPattern::new(color);
|
let pattern = ColorPattern::new(color);
|
||||||
let azure_pattern = pattern.azure_color_pattern;
|
let azure_pattern = pattern.azure_color_pattern;
|
||||||
assert!(!azure_pattern.is_null());
|
assert!(!azure_pattern.is_null());
|
||||||
|
@ -1190,7 +1211,7 @@ impl ScaledFontExtensionMethods for ScaledFont {
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// TODO(Issue #64): this call needs to move into azure_hl.rs
|
// 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(),
|
self.get_ref(),
|
||||||
&mut glyphbuf,
|
&mut glyphbuf,
|
||||||
azure_pattern,
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -536,7 +536,7 @@ impl WorkerThread {
|
||||||
// Build the paint context.
|
// Build the paint context.
|
||||||
let mut paint_context = PaintContext {
|
let mut paint_context = PaintContext {
|
||||||
draw_target: draw_target.clone(),
|
draw_target: draw_target.clone(),
|
||||||
font_ctx: &mut self.font_context,
|
font_context: &mut self.font_context,
|
||||||
page_rect: tile.page_rect,
|
page_rect: tile.page_rect,
|
||||||
screen_rect: tile.screen_rect,
|
screen_rect: tile.screen_rect,
|
||||||
clip_rect: None,
|
clip_rect: None,
|
||||||
|
|
|
@ -24,8 +24,8 @@ use util::{OpaqueNodeMethods, ToGfxColor};
|
||||||
use geom::approxeq::ApproxEq;
|
use geom::approxeq::ApproxEq;
|
||||||
use geom::{Point2D, Rect, Size2D, SideOffsets2D};
|
use geom::{Point2D, Rect, Size2D, SideOffsets2D};
|
||||||
use gfx::color;
|
use gfx::color;
|
||||||
use gfx::display_list::{BOX_SHADOW_INFLATION_FACTOR, BaseDisplayItem, BorderDisplayItem};
|
use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDisplayItem};
|
||||||
use gfx::display_list::{BorderRadii, BoxShadowDisplayItem, ClippingRegion};
|
use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion};
|
||||||
use gfx::display_list::{DisplayItem, DisplayList, DisplayItemMetadata};
|
use gfx::display_list::{DisplayItem, DisplayList, DisplayItemMetadata};
|
||||||
use gfx::display_list::{GradientDisplayItem};
|
use gfx::display_list::{GradientDisplayItem};
|
||||||
use gfx::display_list::{GradientStop, ImageDisplayItem, LineDisplayItem};
|
use gfx::display_list::{GradientStop, ImageDisplayItem, LineDisplayItem};
|
||||||
|
@ -39,7 +39,7 @@ use msg::constellation_msg::Msg as ConstellationMsg;
|
||||||
use msg::constellation_msg::ConstellationChan;
|
use msg::constellation_msg::ConstellationChan;
|
||||||
use net::image::holder::ImageHolder;
|
use net::image::holder::ImageHolder;
|
||||||
use servo_util::cursor::Cursor;
|
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::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
|
||||||
use servo_util::opts;
|
use servo_util::opts;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
@ -196,12 +196,17 @@ pub trait FragmentDisplayListBuilding {
|
||||||
stacking_relative_border_box: &Rect<Au>)
|
stacking_relative_border_box: &Rect<Au>)
|
||||||
-> ClippingRegion;
|
-> 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,
|
fn build_display_list_for_text_fragment(&self,
|
||||||
display_list: &mut DisplayList,
|
display_list: &mut DisplayList,
|
||||||
text_fragment: &ScannedTextFragmentInfo,
|
text_fragment: &ScannedTextFragmentInfo,
|
||||||
text_color: RGBA,
|
text_color: RGBA,
|
||||||
stacking_relative_content_box: &Rect<Au>,
|
stacking_relative_content_box: &Rect<Au>,
|
||||||
|
shadow_blur_radius: Option<Au>,
|
||||||
|
offset: &Point2D<Au>,
|
||||||
clip: &ClippingRegion);
|
clip: &ClippingRegion);
|
||||||
|
|
||||||
/// Creates the display item for a text decoration: underline, overline, or line-through.
|
/// Creates the display item for a text decoration: underline, overline, or line-through.
|
||||||
|
@ -209,7 +214,8 @@ pub trait FragmentDisplayListBuilding {
|
||||||
display_list: &mut DisplayList,
|
display_list: &mut DisplayList,
|
||||||
color: &RGBA,
|
color: &RGBA,
|
||||||
stacking_relative_box: &LogicalRect<Au>,
|
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.
|
/// A helper method that `build_display_list` calls to create per-fragment-type display items.
|
||||||
fn build_fragment_type_specific_display_items(&mut self,
|
fn build_fragment_type_specific_display_items(&mut self,
|
||||||
|
@ -535,11 +541,10 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
clip: &ClippingRegion) {
|
clip: &ClippingRegion) {
|
||||||
// NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back).
|
// 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() {
|
for box_shadow in style.get_effects().box_shadow.iter().rev() {
|
||||||
let inflation = box_shadow.spread_radius + box_shadow.blur_radius *
|
let bounds = shadow_bounds(&absolute_bounds.translate(&Point2D(box_shadow.offset_x,
|
||||||
BOX_SHADOW_INFLATION_FACTOR;
|
box_shadow.offset_y)),
|
||||||
let bounds =
|
box_shadow.blur_radius,
|
||||||
absolute_bounds.translate(&Point2D(box_shadow.offset_x, box_shadow.offset_y))
|
box_shadow.spread_radius);
|
||||||
.inflate(inflation, inflation);
|
|
||||||
list.push(DisplayItem::BoxShadowClass(box BoxShadowDisplayItem {
|
list.push(DisplayItem::BoxShadowClass(box BoxShadowDisplayItem {
|
||||||
base: BaseDisplayItem::new(bounds,
|
base: BaseDisplayItem::new(bounds,
|
||||||
DisplayItemMetadata::new(self.node,
|
DisplayItemMetadata::new(self.node,
|
||||||
|
@ -551,7 +556,11 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
offset: Point2D(box_shadow.offset_x, box_shadow.offset_y),
|
offset: Point2D(box_shadow.offset_x, box_shadow.offset_y),
|
||||||
blur_radius: box_shadow.blur_radius,
|
blur_radius: box_shadow.blur_radius,
|
||||||
spread_radius: box_shadow.spread_radius,
|
spread_radius: box_shadow.spread_radius,
|
||||||
inset: box_shadow.inset,
|
clip_mode: if box_shadow.inset {
|
||||||
|
BoxShadowClipMode::Inset
|
||||||
|
} else {
|
||||||
|
BoxShadowClipMode::Outset
|
||||||
|
},
|
||||||
}), level);
|
}), level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -841,19 +850,31 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
self.stacking_relative_content_box(stacking_relative_border_box);
|
self.stacking_relative_content_box(stacking_relative_border_box);
|
||||||
|
|
||||||
match self.specific {
|
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) => {
|
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;
|
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,
|
self.build_display_list_for_text_fragment(display_list,
|
||||||
&**text_fragment,
|
&**text_fragment,
|
||||||
text_color,
|
text_color,
|
||||||
&stacking_relative_content_box,
|
&stacking_relative_content_box,
|
||||||
|
None,
|
||||||
|
&Point2D(Au(0), Au(0)),
|
||||||
clip);
|
clip);
|
||||||
|
|
||||||
if opts::get().show_debug_fragment_borders {
|
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));
|
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_fragment: &ScannedTextFragmentInfo,
|
||||||
text_color: RGBA,
|
text_color: RGBA,
|
||||||
stacking_relative_content_box: &Rect<Au>,
|
stacking_relative_content_box: &Rect<Au>,
|
||||||
|
shadow_blur_radius: Option<Au>,
|
||||||
|
offset: &Point2D<Au>,
|
||||||
clip: &ClippingRegion) {
|
clip: &ClippingRegion) {
|
||||||
// Determine the orientation and cursor to use.
|
// Determine the orientation and cursor to use.
|
||||||
let (orientation, cursor) = if self.style.writing_mode.is_vertical() {
|
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.
|
// FIXME(pcwalton): Get the real container size.
|
||||||
let container_size = Size2D::zero();
|
let container_size = Size2D::zero();
|
||||||
let metrics = &text_fragment.run.font_metrics;
|
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 +
|
let baseline_origin = stacking_relative_content_box.origin +
|
||||||
LogicalPoint::new(self.style.writing_mode,
|
LogicalPoint::new(self.style.writing_mode,
|
||||||
Au(0),
|
Au(0),
|
||||||
|
@ -1009,7 +1039,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
|
|
||||||
// Create the text display item.
|
// Create the text display item.
|
||||||
display_list.content.push_back(DisplayItem::TextClass(box TextDisplayItem {
|
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),
|
DisplayItemMetadata::new(self.node, self.style(), cursor),
|
||||||
(*clip).clone()),
|
(*clip).clone()),
|
||||||
text_run: text_fragment.run.clone(),
|
text_run: text_fragment.run.clone(),
|
||||||
|
@ -1017,13 +1047,23 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
text_color: text_color.to_gfx_color(),
|
text_color: text_color.to_gfx_color(),
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
baseline_origin: baseline_origin,
|
baseline_origin: baseline_origin,
|
||||||
|
blur_radius: shadow_blur_radius.unwrap_or(Au(0)),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Create display items for text decorations.
|
// 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 =
|
let stacking_relative_content_box =
|
||||||
LogicalRect::from_physical(self.style.writing_mode,
|
LogicalRect::from_physical(self.style.writing_mode,
|
||||||
*stacking_relative_content_box,
|
stacking_relative_content_box,
|
||||||
container_size);
|
container_size);
|
||||||
if let Some(ref underline_color) = text_decorations.underline {
|
if let Some(ref underline_color) = text_decorations.underline {
|
||||||
let mut stacking_relative_box = stacking_relative_content_box;
|
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,
|
self.build_display_list_for_text_decoration(display_list,
|
||||||
underline_color,
|
underline_color,
|
||||||
&stacking_relative_box,
|
&stacking_relative_box,
|
||||||
clip)
|
clip,
|
||||||
|
shadow_blur_radius.unwrap_or(Au(0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref overline_color) = text_decorations.overline {
|
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,
|
self.build_display_list_for_text_decoration(display_list,
|
||||||
overline_color,
|
overline_color,
|
||||||
&stacking_relative_box,
|
&stacking_relative_box,
|
||||||
clip)
|
clip,
|
||||||
|
shadow_blur_radius.unwrap_or(Au(0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref line_through_color) = text_decorations.line_through {
|
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,
|
self.build_display_list_for_text_decoration(display_list,
|
||||||
line_through_color,
|
line_through_color,
|
||||||
&stacking_relative_box,
|
&stacking_relative_box,
|
||||||
clip)
|
clip,
|
||||||
|
shadow_blur_radius.unwrap_or(Au(0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1061,16 +1104,27 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
display_list: &mut DisplayList,
|
display_list: &mut DisplayList,
|
||||||
color: &RGBA,
|
color: &RGBA,
|
||||||
stacking_relative_box: &LogicalRect<Au>,
|
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.
|
// FIXME(pcwalton, #2795): Get the real container size.
|
||||||
let container_size = Size2D::zero();
|
let container_size = Size2D::zero();
|
||||||
let stacking_relative_box = stacking_relative_box.to_physical(self.style.writing_mode,
|
let stacking_relative_box = stacking_relative_box.to_physical(self.style.writing_mode,
|
||||||
container_size);
|
container_size);
|
||||||
|
|
||||||
let metadata = DisplayItemMetadata::new(self.node, &*self.style, Cursor::DefaultCursor);
|
let metadata = DisplayItemMetadata::new(self.node, &*self.style, Cursor::DefaultCursor);
|
||||||
display_list.content.push_back(DisplayItem::SolidColorClass(box SolidColorDisplayItem {
|
display_list.content.push_back(DisplayItem::BoxShadowClass(box BoxShadowDisplayItem {
|
||||||
base: BaseDisplayItem::new(stacking_relative_box, metadata, (*clip).clone()),
|
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(),
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ use wrapper::{TLayoutNode, ThreadSafeLayoutNode};
|
||||||
|
|
||||||
use geom::num::Zero;
|
use geom::num::Zero;
|
||||||
use geom::{Point2D, Rect, Size2D};
|
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::glyph::CharIndex;
|
||||||
use gfx::text::text_run::{TextRun, TextRunSlice};
|
use gfx::text::text_run::{TextRun, TextRunSlice};
|
||||||
use script_traits::UntrustedNodeAddress;
|
use script_traits::UntrustedNodeAddress;
|
||||||
|
@ -2043,8 +2043,8 @@ impl Fragment {
|
||||||
// Box shadows cause us to draw outside our border box.
|
// Box shadows cause us to draw outside our border box.
|
||||||
for box_shadow in self.style().get_effects().box_shadow.iter() {
|
for box_shadow in self.style().get_effects().box_shadow.iter() {
|
||||||
let offset = Point2D(box_shadow.offset_x, box_shadow.offset_y);
|
let offset = Point2D(box_shadow.offset_x, box_shadow.offset_y);
|
||||||
let inflation = box_shadow.spread_radius +
|
let inflation = box_shadow.spread_radius + box_shadow.blur_radius *
|
||||||
box_shadow.blur_radius * BOX_SHADOW_INFLATION_FACTOR;
|
BLUR_INFLATION_FACTOR;
|
||||||
overflow = overflow.union(&border_box.translate(&offset).inflate(inflation, inflation))
|
overflow = overflow.union(&border_box.translate(&offset).inflate(inflation, inflation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,7 @@ partial interface CSSStyleDeclaration {
|
||||||
|
|
||||||
[TreatNullAs=EmptyString] attribute DOMString boxSizing;
|
[TreatNullAs=EmptyString] attribute DOMString boxSizing;
|
||||||
[TreatNullAs=EmptyString] attribute DOMString boxShadow;
|
[TreatNullAs=EmptyString] attribute DOMString boxShadow;
|
||||||
|
[TreatNullAs=EmptyString] attribute DOMString textShadow;
|
||||||
|
|
||||||
//[TreatNullAs=EmptyString] attribute DOMString float; //XXXjdm need BinaryName annotation
|
//[TreatNullAs=EmptyString] attribute DOMString float; //XXXjdm need BinaryName annotation
|
||||||
|
|
||||||
|
|
|
@ -292,7 +292,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "geom"
|
name = "geom"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/servo/rust-geom#6b079ba2738ed15bac2b6ec66850494afb9f2b4c"
|
source = "git+https://github.com/servo/rust-geom#876c2fceee211130d1294eacdc1bd8742c52540e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustc-serialize 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
|
@ -2030,6 +2030,171 @@ pub mod longhands {
|
||||||
}
|
}
|
||||||
</%self:longhand>
|
</%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">
|
<%self:longhand name="filter">
|
||||||
use values::specified::Angle;
|
use values::specified::Angle;
|
||||||
pub use self::computed_value::T as SpecifiedValue;
|
pub use self::computed_value::T as SpecifiedValue;
|
||||||
|
|
|
@ -295,7 +295,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "geom"
|
name = "geom"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/servo/rust-geom#6b079ba2738ed15bac2b6ec66850494afb9f2b4c"
|
source = "git+https://github.com/servo/rust-geom#876c2fceee211130d1294eacdc1bd8742c52540e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustc-serialize 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
|
@ -257,7 +257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "geom"
|
name = "geom"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/servo/rust-geom#6b079ba2738ed15bac2b6ec66850494afb9f2b4c"
|
source = "git+https://github.com/servo/rust-geom#876c2fceee211130d1294eacdc1bd8742c52540e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustc-serialize 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
Загрузка…
Ссылка в новой задаче