Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

layout: Implement `text-shadow` per CSS-TEXT-DECORATION-3 § 4. #4475

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

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

Large diffs are not rendered by default.

@@ -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,20 +196,26 @@ 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.
fn build_display_list_for_text_decoration(&self,
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,21 +1039,31 @@ 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(),
range: text_fragment.range,
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,24 +1095,36 @@ 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)))
}
}

fn build_display_list_for_text_decoration(&self,
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

Some generated files are not rendered by default. Learn more.

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.