From b12f87d947413eb1b457926f3d3ef4c5d9dc00b7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 6 Nov 2014 10:00:42 -0700 Subject: [PATCH] servo: Merge #3696 - layout: Implement a subset of CSS linear gradients per the CSS-IMAGES specification (from pcwalton:gradients); r=SimonSapin This implements a subset of the CSS `linear-gradient` property per the CSS-IMAGES specification: http://dev.w3.org/csswg/css-images-3/ The full syntax is parsed, but only the beginning and end color stops are used, and gradients are clamped to the nearest 90 degrees. It should not be architecturally difficult to add the remaining pieces, but in the interests of bounding the size of this patch that work has been left to follow-ups. Improves GitHub. r? @glennw Source-Repo: https://github.com/servo/servo Source-Revision: ed22c9b35b64ccf7a68ed4f26b1eecfa39996efd --- servo/components/gfx/display_list/mod.rs | 32 ++ servo/components/gfx/render_context.rs | 53 +++- .../components/layout/display_list_builder.rs | 201 +++++++++++- servo/components/style/lib.rs | 5 +- .../style/properties/common_types.rs | 291 ++++++++++++++++++ servo/components/style/properties/mod.rs.mako | 54 ++-- 6 files changed, 594 insertions(+), 42 deletions(-) diff --git a/servo/components/gfx/display_list/mod.rs b/servo/components/gfx/display_list/mod.rs index 75861005dce2..ef4587a1393c 100644 --- a/servo/components/gfx/display_list/mod.rs +++ b/servo/components/gfx/display_list/mod.rs @@ -33,6 +33,10 @@ use std::slice::Items; use style::computed_values::border_style; use sync::Arc; +// It seems cleaner to have layout code not mention Azure directly, so let's just reexport this for +// layout to use. +pub use azure::azure_hl::GradientStop; + pub mod optimizer; /// An opaque handle to a node. The only safe operation that can be performed on this node is to @@ -295,6 +299,7 @@ pub enum DisplayItem { TextDisplayItemClass(Box), ImageDisplayItemClass(Box), BorderDisplayItemClass(Box), + GradientDisplayItemClass(Box), LineDisplayItemClass(Box), /// A pseudo-display item that exists only so that queries like `ContentBoxQuery` and @@ -382,6 +387,22 @@ pub struct ImageDisplayItem { pub stretch_size: Size2D, } +/// Paints a gradient. +#[deriving(Clone)] +pub struct GradientDisplayItem { + /// Fields common to all display items. + pub base: BaseDisplayItem, + + /// The start point of the gradient (computed during display list construction). + pub start_point: Point2D, + + /// The end point of the gradient (computed during display list construction). + pub end_point: Point2D, + + /// A list of color stops. + pub stops: Vec, +} + /// Renders a border. #[deriving(Clone)] pub struct BorderDisplayItem { @@ -482,6 +503,13 @@ impl DisplayItem { border.style) } + GradientDisplayItemClass(ref gradient) => { + render_context.draw_linear_gradient(&gradient.base.bounds, + &gradient.start_point, + &gradient.end_point, + gradient.stops.as_slice()); + } + LineDisplayItemClass(ref line) => { render_context.draw_line(&line.base.bounds, line.color, @@ -498,6 +526,7 @@ impl DisplayItem { TextDisplayItemClass(ref text) => &text.base, ImageDisplayItemClass(ref image_item) => &image_item.base, BorderDisplayItemClass(ref border) => &border.base, + GradientDisplayItemClass(ref gradient) => &gradient.base, LineDisplayItemClass(ref line) => &line.base, PseudoDisplayItemClass(ref base) => &**base, } @@ -509,6 +538,7 @@ impl DisplayItem { TextDisplayItemClass(ref mut text) => &mut text.base, ImageDisplayItemClass(ref mut image_item) => &mut image_item.base, BorderDisplayItemClass(ref mut border) => &mut border.base, + GradientDisplayItemClass(ref mut gradient) => &mut gradient.base, LineDisplayItemClass(ref mut line) => &mut line.base, PseudoDisplayItemClass(ref mut base) => &mut **base, } @@ -535,6 +565,7 @@ impl fmt::Show for DisplayItem { TextDisplayItemClass(_) => "Text", ImageDisplayItemClass(_) => "Image", BorderDisplayItemClass(_) => "Border", + GradientDisplayItemClass(_) => "Gradient", LineDisplayItemClass(_) => "Line", PseudoDisplayItemClass(_) => "Pseudo", }, @@ -544,3 +575,4 @@ impl fmt::Show for DisplayItem { ) } } + diff --git a/servo/components/gfx/render_context.rs b/servo/components/gfx/render_context.rs index b682b8f5f02b..0a26c4a761fc 100644 --- a/servo/components/gfx/render_context.rs +++ b/servo/components/gfx/render_context.rs @@ -2,22 +2,23 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use display_list::{SidewaysLeft, SidewaysRight, TextDisplayItem, Upright}; -use font_context::FontContext; -use style::computed_values::border_style; +//! Painting of display lists using Moz2D/Azure. use azure::azure_hl::{B8G8R8A8, A8, Color, ColorPattern, ColorPatternRef, DrawOptions}; -use azure::azure_hl::{DrawSurfaceOptions,DrawTarget, Linear, SourceOp, StrokeOptions}; +use azure::azure_hl::{DrawSurfaceOptions, DrawTarget, ExtendClamp, GradientStop, Linear}; +use azure::azure_hl::{LinearGradientPattern, LinearGradientPatternRef, SourceOp, StrokeOptions}; use azure::scaled_font::ScaledFont; -use azure::{AZ_CAP_BUTT, AzDrawTargetFillGlyphs, AzFloat, struct__AzDrawOptions, struct__AzGlyph}; -use azure::{struct__AzGlyphBuffer, struct__AzPoint}; +use azure::{AZ_CAP_BUTT, AzFloat, struct__AzDrawOptions, struct__AzGlyph}; +use azure::{struct__AzGlyphBuffer, struct__AzPoint, AzDrawTargetFillGlyphs}; +use display_list::{SidewaysLeft, SidewaysRight, TextDisplayItem, Upright}; +use font_context::FontContext; use geom::matrix2d::Matrix2D; use geom::point::Point2D; use geom::rect::Rect; -use geom::size::Size2D; use geom::side_offsets::SideOffsets2D; -use libc::types::common::c99::{uint16_t, uint32_t}; +use geom::size::Size2D; use libc::size_t; +use libc::types::common::c99::{uint16_t, uint32_t}; use png::{RGB8, RGBA8, K8, KA8}; use servo_net::image::base::Image; use servo_util::geometry::Au; @@ -25,9 +26,10 @@ use servo_util::opts; use servo_util::range::Range; use std::num::Zero; use std::ptr; +use style::computed_values::border_style; use sync::Arc; -use text::glyph::CharIndex; use text::TextRun; +use text::glyph::CharIndex; pub struct RenderContext<'a> { pub draw_target: &'a DrawTarget, @@ -443,6 +445,36 @@ impl<'a> RenderContext<'a> { self.draw_target.set_transform(current_transform) } } + + /// Draws a linear gradient in the given boundaries from the given start point to the given end + /// point with the given stops. + pub fn draw_linear_gradient(&self, + bounds: &Rect, + start_point: &Point2D, + end_point: &Point2D, + stops: &[GradientStop]) { + self.draw_target.make_current(); + + let stops = self.draw_target.create_gradient_stops(stops, ExtendClamp); + let pattern = LinearGradientPattern::new(&start_point.to_azure_point(), + &end_point.to_azure_point(), + stops, + &Matrix2D::identity()); + + self.draw_target.fill_rect(&bounds.to_azure_rect(), + LinearGradientPatternRef(&pattern), + None); + } +} + +trait ToAzurePoint { + fn to_azure_point(&self) -> Point2D; +} + +impl ToAzurePoint for Point2D { + fn to_azure_point(&self) -> Point2D { + Point2D(self.x.to_nearest_px() as AzFloat, self.y.to_nearest_px() as AzFloat) + } } trait ToAzureRect { @@ -451,8 +483,7 @@ trait ToAzureRect { impl ToAzureRect for Rect { fn to_azure_rect(&self) -> Rect { - Rect(Point2D(self.origin.x.to_nearest_px() as AzFloat, - self.origin.y.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)) } diff --git a/servo/components/layout/display_list_builder.rs b/servo/components/layout/display_list_builder.rs index ecc1236d9c52..4a6da7b514bd 100644 --- a/servo/components/layout/display_list_builder.rs +++ b/servo/components/layout/display_list_builder.rs @@ -27,8 +27,9 @@ use geom::{Point2D, Rect, Size2D, SideOffsets2D}; use gfx::color; use gfx::display_list::{BackgroundAndBorderLevel, BaseDisplayItem, BorderDisplayItem}; use gfx::display_list::{BorderDisplayItemClass, ContentStackingLevel, DisplayList}; -use gfx::display_list::{FloatStackingLevel, ImageDisplayItem, ImageDisplayItemClass}; -use gfx::display_list::{LineDisplayItem, LineDisplayItemClass, PositionedDescendantStackingLevel}; +use gfx::display_list::{FloatStackingLevel, GradientDisplayItem, GradientDisplayItemClass}; +use gfx::display_list::{GradientStop, ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem}; +use gfx::display_list::{LineDisplayItemClass, PositionedDescendantStackingLevel}; use gfx::display_list::{PseudoDisplayItemClass, RootOfStackingContextLevel, SidewaysLeft}; use gfx::display_list::{SidewaysRight, SolidColorDisplayItem, SolidColorDisplayItemClass}; use gfx::display_list::{StackingLevel, TextDisplayItem, TextDisplayItemClass, Upright}; @@ -41,10 +42,13 @@ use servo_util::geometry::{mod, Au, ZERO_RECT}; use servo_util::logical_geometry::{LogicalRect, WritingMode}; use servo_util::opts; use std::mem; -use style::{ComputedValues, RGBA}; +use style::computed::{AngleAoc, CornerAoc, LP_Length, LP_Percentage, LengthOrPercentage}; +use style::computed::{LinearGradient, LinearGradientImage, UrlImage}; use style::computed_values::{background_attachment, background_repeat, border_style, overflow}; use style::computed_values::{visibility}; +use style::{ComputedValues, Bottom, Left, RGBA, Right, Top}; use sync::Arc; +use url::Url; pub trait FragmentDisplayListBuilding { /// Adds the display items necessary to paint the background of this fragment to the display @@ -57,6 +61,27 @@ pub trait FragmentDisplayListBuilding { absolute_bounds: &Rect, clip_rect: &Rect); + /// Adds the display items necessary to paint the background image of this fragment to the + /// display list at the appropriate stacking level. + fn build_display_list_for_background_image(&self, + style: &ComputedValues, + list: &mut DisplayList, + layout_context: &LayoutContext, + level: StackingLevel, + absolute_bounds: &Rect, + clip_rect: &Rect, + image_url: &Url); + + /// Adds the display items necessary to paint the background linear gradient of this fragment + /// to the display list at the appropriate stacking level. + fn build_display_list_for_background_linear_gradient(&self, + list: &mut DisplayList, + level: StackingLevel, + absolute_bounds: &Rect, + clip_rect: &Rect, + gradient: &LinearGradient, + style: &ComputedValues); + /// Adds the display items necessary to paint the borders of this fragment to a display list if /// necessary. fn build_display_list_for_borders_if_applicable(&self, @@ -128,11 +153,37 @@ impl FragmentDisplayListBuilding for Fragment { // Implements background image, per spec: // http://www.w3.org/TR/CSS21/colors.html#background let background = style.get_background(); - let image_url = match background.background_image { - None => return, - Some(ref image_url) => image_url, - }; + match background.background_image { + None => {} + Some(LinearGradientImage(ref gradient)) => { + self.build_display_list_for_background_linear_gradient(list, + level, + absolute_bounds, + clip_rect, + gradient, + style) + } + Some(UrlImage(ref image_url)) => { + self.build_display_list_for_background_image(style, + list, + layout_context, + level, + absolute_bounds, + clip_rect, + image_url) + } + } + } + fn build_display_list_for_background_image(&self, + style: &ComputedValues, + list: &mut DisplayList, + layout_context: &LayoutContext, + level: StackingLevel, + absolute_bounds: &Rect, + clip_rect: &Rect, + image_url: &Url) { + let background = style.get_background(); let mut holder = ImageHolder::new(image_url.clone(), layout_context.shared.image_cache.clone()); let image = match holder.get_image(self.node.to_untrusted_node_address()) { @@ -212,8 +263,116 @@ impl FragmentDisplayListBuilding for Fragment { })); } - /// Adds the display items necessary to paint the borders of this fragment to a display list if - /// necessary. + fn build_display_list_for_background_linear_gradient(&self, + list: &mut DisplayList, + level: StackingLevel, + absolute_bounds: &Rect, + clip_rect: &Rect, + gradient: &LinearGradient, + style: &ComputedValues) { + let clip_rect = clip_rect.intersection(absolute_bounds).unwrap_or(ZERO_RECT); + + // This is the distance between the center and the ending point; i.e. half of the distance + // between the starting point and the ending point. + let delta = match gradient.angle_or_corner { + AngleAoc(angle) => { + Point2D(Au((angle.radians().sin() * + absolute_bounds.size.width.to_f64().unwrap() / 2.0) as i32), + Au((-angle.radians().cos() * + absolute_bounds.size.height.to_f64().unwrap() / 2.0) as i32)) + } + CornerAoc(horizontal, vertical) => { + let x_factor = match horizontal { + Left => -1, + Right => 1, + }; + let y_factor = match vertical { + Top => -1, + Bottom => 1, + }; + Point2D(Au(x_factor * absolute_bounds.size.width.to_i32().unwrap() / 2), + Au(y_factor * absolute_bounds.size.height.to_i32().unwrap() / 2)) + } + }; + + // This is the length of the gradient line. + let length = Au((delta.x.to_f64().unwrap() * 2.0).hypot(delta.y.to_f64().unwrap() * 2.0) + as i32); + + // Determine the position of each stop per CSS-IMAGES § 3.4. + // + // FIXME(#3908, pcwalton): Make sure later stops can't be behind earlier stops. + let (mut stops, mut stop_run) = (Vec::new(), None); + for (i, stop) in gradient.stops.iter().enumerate() { + let offset = match stop.position { + None => { + if stop_run.is_none() { + // Initialize a new stop run. + let start_offset = if i == 0 { + 0.0 + } else { + // `unwrap()` here should never fail because this is the beginning of + // a stop run, which is always bounded by a length or percentage. + position_to_offset(gradient.stops[i - 1].position.unwrap(), length) + }; + let (end_index, end_offset) = + match gradient.stops + .as_slice() + .slice_from(i) + .iter() + .enumerate() + .find(|&(_, ref stop)| stop.position.is_some()) { + None => (gradient.stops.len() - 1, 1.0), + Some((end_index, end_stop)) => { + // `unwrap()` here should never fail because this is the end of + // a stop run, which is always bounded by a length or + // percentage. + (end_index, + position_to_offset(end_stop.position.unwrap(), length)) + } + }; + stop_run = Some(StopRun { + start_offset: start_offset, + end_offset: end_offset, + start_index: i, + stop_count: end_index - i, + }) + } + + let stop_run = stop_run.unwrap(); + let stop_run_length = stop_run.end_offset - stop_run.start_offset; + if stop_run.stop_count == 0 { + stop_run.end_offset + } else { + stop_run.start_offset + + stop_run_length * (i - stop_run.start_index) as f32 / + (stop_run.stop_count as f32) + } + } + Some(position) => { + stop_run = None; + position_to_offset(position, length) + } + }; + stops.push(GradientStop { + offset: offset, + color: style.resolve_color(stop.color).to_gfx_color() + }) + } + + let center = Point2D(absolute_bounds.origin.x + absolute_bounds.size.width / 2, + absolute_bounds.origin.y + absolute_bounds.size.height / 2); + + let gradient_display_item = GradientDisplayItemClass(box GradientDisplayItem { + base: BaseDisplayItem::new(*absolute_bounds, self.node, level, clip_rect), + start_point: center - delta, + end_point: center + delta, + stops: stops, + }); + + list.push(gradient_display_item) + } + fn build_display_list_for_borders_if_applicable(&self, style: &ComputedValues, list: &mut DisplayList, @@ -686,3 +845,27 @@ impl BlockFlowDisplayListBuilding for BlockFlow { self.base.display_list.flatten(FloatStackingLevel) } } + +// A helper data structure for gradients. +struct StopRun { + start_offset: f32, + end_offset: f32, + start_index: uint, + stop_count: uint, +} + +fn fmin(a: f32, b: f32) -> f32 { + if a < b { + a + } else { + b + } +} + +fn position_to_offset(position: LengthOrPercentage, Au(total_length): Au) -> f32 { + match position { + LP_Length(Au(length)) => fmin(1.0, (length as f32) / (total_length as f32)), + LP_Percentage(percentage) => percentage as f32, + } +} + diff --git a/servo/components/style/lib.rs b/servo/components/style/lib.rs index ca06adda337b..c2af6b6d0640 100644 --- a/servo/components/style/lib.rs +++ b/servo/components/style/lib.rs @@ -43,11 +43,12 @@ pub use selector_matching::{CommonStyleAffectingAttributeInfo, CommonStyleAffect pub use selector_matching::{AttrIsPresentMode, AttrIsEqualMode}; pub use selector_matching::{matches, matches_simple_selector, common_style_affecting_attributes}; pub use selector_matching::{RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE,SELECTOR_WHITESPACE}; -pub use properties::{cascade, cascade_anonymous}; +pub use properties::{cascade, cascade_anonymous, computed}; pub use properties::{PropertyDeclaration, ComputedValues, computed_values, style_structs}; pub use properties::{PropertyDeclarationBlock, parse_style_attribute}; // Style attributes pub use properties::{CSSFloat, DeclaredValue, PropertyDeclarationParseResult}; -pub use properties::longhands; +pub use properties::{longhands, Angle, AngleOrCorner, AngleAoc, CornerAoc}; +pub use properties::{Left, Right, Bottom, Top}; pub use node::{TElement, TElementAttributes, TNode}; pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_list_from_str}; pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace}; diff --git a/servo/components/style/properties/common_types.rs b/servo/components/style/properties/common_types.rs index 4ed9f6ea4ad2..fd5316116929 100644 --- a/servo/components/style/properties/common_types.rs +++ b/servo/components/style/properties/common_types.rs @@ -14,8 +14,11 @@ pub static DEFAULT_LINE_HEIGHT: CSSFloat = 1.14; pub mod specified { use std::ascii::StrAsciiExt; + use std::f64::consts::PI; + use url::Url; use cssparser::ast; use cssparser::ast::*; + use parsing_utils::{mod, BufferedIter, ParserIter}; use super::{Au, CSSFloat}; pub use cssparser::Color as CSSColor; @@ -208,13 +211,250 @@ pub mod specified { } } } + + #[deriving(Clone, PartialEq, PartialOrd)] + pub struct Angle(pub CSSFloat); + + impl Angle { + pub fn radians(self) -> f64 { + let Angle(radians) = self; + radians + } + } + + static DEG_TO_RAD: CSSFloat = PI / 180.0; + static GRAD_TO_RAD: CSSFloat = PI / 200.0; + + impl Angle { + /// Parses an angle according to CSS-VALUES § 6.1. + fn parse_dimension(value: CSSFloat, unit: &str) -> Result { + if unit.eq_ignore_ascii_case("deg") { + Ok(Angle(value * DEG_TO_RAD)) + } else if unit.eq_ignore_ascii_case("grad") { + Ok(Angle(value * GRAD_TO_RAD)) + } else if unit.eq_ignore_ascii_case("rad") { + Ok(Angle(value)) + } else if unit.eq_ignore_ascii_case("turn") { + Ok(Angle(value * 2.0 * PI)) + } else { + Err(()) + } + } + } + + /// Specified values for an image according to CSS-IMAGES. + #[deriving(Clone)] + pub enum Image { + UrlImage(Url), + LinearGradientImage(LinearGradient), + } + + impl Image { + pub fn from_component_value(component_value: &ComponentValue, base_url: &Url) + -> Result { + match component_value { + &ast::URL(ref url) => { + let image_url = super::parse_url(url.as_slice(), base_url); + Ok(UrlImage(image_url)) + }, + &ast::Function(ref name, ref args) => { + if name.as_slice().eq_ignore_ascii_case("linear-gradient") { + Ok(LinearGradientImage(try!( + super::specified::LinearGradient::parse_function( + args.as_slice())))) + } else { + Err(()) + } + } + _ => Err(()), + } + } + + pub fn to_computed_value(self, context: &super::computed::Context) + -> super::computed::Image { + match self { + UrlImage(url) => super::computed::UrlImage(url), + LinearGradientImage(linear_gradient) => { + super::computed::LinearGradientImage( + super::computed::LinearGradient::compute(linear_gradient, context)) + } + } + } + } + + /// Specified values for a CSS linear gradient. + #[deriving(Clone)] + pub struct LinearGradient { + /// The angle or corner of the gradient. + pub angle_or_corner: AngleOrCorner, + + /// The color stops. + pub stops: Vec, + } + + /// Specified values for an angle or a corner in a linear gradient. + #[deriving(Clone, PartialEq)] + pub enum AngleOrCorner { + AngleAoc(Angle), + CornerAoc(HorizontalDirection, VerticalDirection), + } + + /// Specified values for one color stop in a linear gradient. + #[deriving(Clone)] + pub struct ColorStop { + /// The color of this stop. + pub color: CSSColor, + + /// The position of this stop. If not specified, this stop is placed halfway between the + /// point that precedes it and the point that follows it. + pub position: Option, + } + + #[deriving(Clone, PartialEq)] + pub enum HorizontalDirection { + Left, + Right, + } + + #[deriving(Clone, PartialEq)] + pub enum VerticalDirection { + Top, + Bottom, + } + + fn parse_color_stop(source: ParserIter) -> Result { + let color = match source.next() { + Some(color) => try!(CSSColor::parse(color)), + None => return Err(()), + }; + + let position = match source.next() { + None => None, + Some(value) => { + match *value { + Comma => { + source.push_back(value); + None + } + ref position => Some(try!(LengthOrPercentage::parse(position))), + } + } + }; + + Ok(ColorStop { + color: color, + position: position, + }) + } + + impl LinearGradient { + /// Parses a linear gradient from the given arguments. + pub fn parse_function(args: &[ComponentValue]) -> Result { + let mut source = BufferedIter::new(args.skip_whitespace()); + + // Parse the angle. + let (angle_or_corner, need_to_parse_comma) = match source.next() { + None => return Err(()), + Some(token) => { + match *token { + Dimension(ref value, ref unit) => { + match Angle::parse_dimension(value.value, unit.as_slice()) { + Ok(angle) => { + (AngleAoc(angle), true) + } + Err(()) => { + source.push_back(token); + (AngleAoc(Angle(PI)), false) + } + } + } + Ident(ref ident) if ident.as_slice().eq_ignore_ascii_case("to") => { + let (mut horizontal, mut vertical) = (None, None); + loop { + match source.next() { + None => break, + Some(token) => { + match *token { + Ident(ref ident) => { + let ident = ident.as_slice(); + if ident.eq_ignore_ascii_case("top") && + vertical.is_none() { + vertical = Some(Top) + } else if ident.eq_ignore_ascii_case("bottom") && + vertical.is_none() { + vertical = Some(Bottom) + } else if ident.eq_ignore_ascii_case("left") && + horizontal.is_none() { + horizontal = Some(Left) + } else if ident.eq_ignore_ascii_case("right") && + horizontal.is_none() { + horizontal = Some(Right) + } else { + return Err(()) + } + } + Comma => { + source.push_back(token); + break + } + _ => return Err(()), + } + } + } + } + + (match (horizontal, vertical) { + (None, Some(Top)) => AngleAoc(Angle(0.0)), + (Some(Right), None) => AngleAoc(Angle(PI * 0.5)), + (None, Some(Bottom)) => AngleAoc(Angle(PI)), + (Some(Left), None) => AngleAoc(Angle(PI * 1.5)), + (Some(horizontal), Some(vertical)) => { + CornerAoc(horizontal, vertical) + } + (None, None) => return Err(()), + }, true) + } + _ => { + source.push_back(token); + (AngleAoc(Angle(PI)), false) + } + } + } + }; + + // Parse the color stops. + let stops = if need_to_parse_comma { + match source.next() { + Some(&Comma) => { + try!(parsing_utils::parse_comma_separated(&mut source, parse_color_stop)) + } + None => Vec::new(), + Some(_) => return Err(()), + } + } else { + try!(parsing_utils::parse_comma_separated(&mut source, parse_color_stop)) + }; + + if stops.len() < 2 { + return Err(()) + } + + Ok(LinearGradient { + angle_or_corner: angle_or_corner, + stops: stops, + }) + } + } } pub mod computed { + pub use super::specified::{Angle, AngleAoc, AngleOrCorner, CornerAoc, HorizontalDirection}; + pub use super::specified::{VerticalDirection}; pub use cssparser::Color as CSSColor; pub use super::super::longhands::computed_as_specified as compute_CSSColor; use super::*; use super::super::longhands; + use url::Url; pub struct Context { pub inherited_font_weight: longhands::font_weight::computed_value::T, @@ -309,9 +549,60 @@ pub mod computed { specified::LPN_None => LPN_None, } } + + /// Computed values for an image according to CSS-IMAGES. + #[deriving(Clone, PartialEq)] + pub enum Image { + UrlImage(Url), + LinearGradientImage(LinearGradient), + } + + /// Computed values for a CSS linear gradient. + #[deriving(Clone, PartialEq)] + pub struct LinearGradient { + /// The angle or corner of the gradient. + pub angle_or_corner: AngleOrCorner, + + /// The color stops. + pub stops: Vec, + } + + /// Computed values for one color stop in a linear gradient. + #[deriving(Clone, PartialEq)] + pub struct ColorStop { + /// The color of this stop. + pub color: CSSColor, + + /// The position of this stop. If not specified, this stop is placed halfway between the + /// point that precedes it and the point that follows it per CSS-IMAGES § 3.4. + pub position: Option, + } + + impl LinearGradient { + pub fn compute(value: specified::LinearGradient, context: &Context) -> LinearGradient { + let specified::LinearGradient { + angle_or_corner, + stops + } = value; + LinearGradient { + angle_or_corner: angle_or_corner, + stops: stops.into_iter().map(|stop| { + ColorStop { + color: stop.color, + position: match stop.position { + None => None, + Some(value) => Some(compute_LengthOrPercentage(value, context)), + }, + } + }).collect() + } + } + } } pub fn parse_url(input: &str, base_url: &Url) -> Url { UrlParser::new().base_url(base_url).parse(input) .unwrap_or_else(|_| Url::parse("about:invalid").unwrap()) } + + diff --git a/servo/components/style/properties/mod.rs.mako b/servo/components/style/properties/mod.rs.mako index f036003f237f..b7892a9e28ea 100644 --- a/servo/components/style/properties/mod.rs.mako +++ b/servo/components/style/properties/mod.rs.mako @@ -13,6 +13,8 @@ pub use url::Url; pub use cssparser::*; pub use cssparser::ast::*; pub use geom::SideOffsets2D; +pub use self::common_types::specified::{Angle, AngleAoc, AngleOrCorner, Bottom, CornerAoc}; +pub use self::common_types::specified::{Left, Right, Top}; use errors::{ErrorLoggerIterator, log_css_error}; pub use parsing_utils::*; @@ -602,28 +604,40 @@ pub mod longhands { "RGBAColor(RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")} <%self:single_component_value name="background-image"> - // The computed value is the same as the specified value. - pub use super::computed_as_specified as to_computed_value; - pub mod computed_value { - pub use url::Url; - pub type T = Option; - } - pub type SpecifiedValue = computed_value::T; - #[inline] pub fn get_initial_value() -> SpecifiedValue { - None - } - pub fn from_component_value(component_value: &ComponentValue, base_url: &Url) - -> Result { - match component_value { - &ast::URL(ref url) => { - let image_url = parse_url(url.as_slice(), base_url); - Ok(Some(image_url)) - }, - &ast::Ident(ref value) if value.as_slice().eq_ignore_ascii_case("none") - => Ok(None), - _ => Err(()), + use super::common_types::specified as common_specified; + pub mod computed_value { + use super::super::super::common_types::computed; + #[deriving(Clone, PartialEq)] + pub type T = Option; + } + #[deriving(Clone)] + pub type SpecifiedValue = Option; + #[inline] + pub fn get_initial_value() -> computed_value::T { + None + } + pub fn from_component_value(component_value: &ComponentValue, base_url: &Url) + -> Result { + match component_value { + &ast::Ident(ref value) if value.as_slice().eq_ignore_ascii_case("none") => { + Ok(None) + } + _ => { + match common_specified::Image::from_component_value(component_value, + base_url) { + Err(err) => Err(err), + Ok(result) => Ok(Some(result)), + } } } + } + pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) + -> computed_value::T { + match value { + None => None, + Some(image) => Some(image.to_computed_value(context)), + } + } <%self:longhand name="background-position">