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