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

This commit is contained in:
Patrick Walton 2014-12-18 12:57:58 -08:00
parent fed878710c
commit 09358b908d
19 changed files with 683 additions and 152 deletions

View file

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

View file

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

View file

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