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:
Patrick Walton 2015-03-02 14:54:52 -07:00
Родитель 689a6b3ab5
Коммит 8d21fbc4f5
10 изменённых файлов: 483 добавлений и 152 удалений

Просмотреть файл

@ -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)
}
}
}

Просмотреть файл

@ -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: Dont 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));
// 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.
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);
// 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()
}
// Undo the draw target's clip.
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);
}
}

Просмотреть файл

@ -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,

Просмотреть файл

@ -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)
}

Просмотреть файл

@ -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))
}

Просмотреть файл

@ -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

2
servo/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)",
]

Просмотреть файл

@ -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;

2
servo/ports/cef/Cargo.lock сгенерированный
Просмотреть файл

@ -295,7 +295,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)",
]

2
servo/ports/gonk/Cargo.lock сгенерированный
Просмотреть файл

@ -257,7 +257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[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)",
]