aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/gfx/display_list/mod.rs32
-rw-r--r--components/gfx/render_context.rs53
-rw-r--r--components/layout/display_list_builder.rs201
-rw-r--r--components/style/lib.rs5
-rw-r--r--components/style/properties/common_types.rs291
-rw-r--r--components/style/properties/mod.rs.mako54
-rw-r--r--tests/ref/basic.list5
-rw-r--r--tests/ref/linear_gradients_corners_a.html20
-rw-r--r--tests/ref/linear_gradients_corners_ref.html20
-rw-r--r--tests/ref/linear_gradients_lengths_a.html22
-rw-r--r--tests/ref/linear_gradients_lengths_ref.html22
-rw-r--r--tests/ref/linear_gradients_parsing_a.html28
-rw-r--r--tests/ref/linear_gradients_parsing_ref.html43
-rw-r--r--tests/ref/linear_gradients_reverse_a.html32
-rw-r--r--tests/ref/linear_gradients_reverse_ref.html33
-rw-r--r--tests/ref/linear_gradients_smoke_a.html26
-rw-r--r--tests/ref/linear_gradients_smoke_ref.html20
17 files changed, 865 insertions, 42 deletions
diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs
index 75861005dce..ef4587a1393 100644
--- a/components/gfx/display_list/mod.rs
+++ b/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<TextDisplayItem>),
ImageDisplayItemClass(Box<ImageDisplayItem>),
BorderDisplayItemClass(Box<BorderDisplayItem>),
+ GradientDisplayItemClass(Box<GradientDisplayItem>),
LineDisplayItemClass(Box<LineDisplayItem>),
/// A pseudo-display item that exists only so that queries like `ContentBoxQuery` and
@@ -382,6 +387,22 @@ pub struct ImageDisplayItem {
pub stretch_size: Size2D<Au>,
}
+/// 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<Au>,
+
+ /// The end point of the gradient (computed during display list construction).
+ pub end_point: Point2D<Au>,
+
+ /// A list of color stops.
+ pub stops: Vec<GradientStop>,
+}
+
/// 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/components/gfx/render_context.rs b/components/gfx/render_context.rs
index b682b8f5f02..0a26c4a761f 100644
--- a/components/gfx/render_context.rs
+++ b/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<Au>,
+ start_point: &Point2D<Au>,
+ end_point: &Point2D<Au>,
+ 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<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)
+ }
}
trait ToAzureRect {
@@ -451,8 +483,7 @@ trait ToAzureRect {
impl ToAzureRect for Rect<Au> {
fn to_azure_rect(&self) -> Rect<AzFloat> {
- 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/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs
index ecc1236d9c5..4a6da7b514b 100644
--- a/components/layout/display_list_builder.rs
+++ b/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<Au>,
clip_rect: &Rect<Au>);
+ /// 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<Au>,
+ clip_rect: &Rect<Au>,
+ 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<Au>,
+ clip_rect: &Rect<Au>,
+ 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<Au>,
+ clip_rect: &Rect<Au>,
+ 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<Au>,
+ clip_rect: &Rect<Au>,
+ 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/components/style/lib.rs b/components/style/lib.rs
index ca06adda337..c2af6b6d064 100644
--- a/components/style/lib.rs
+++ b/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/components/style/properties/common_types.rs b/components/style/properties/common_types.rs
index 4ed9f6ea4ad..fd531611692 100644
--- a/components/style/properties/common_types.rs
+++ b/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<Angle,()> {
+ 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<Image,()> {
+ 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<ColorStop>,
+ }
+
+ /// 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<LengthOrPercentage>,
+ }
+
+ #[deriving(Clone, PartialEq)]
+ pub enum HorizontalDirection {
+ Left,
+ Right,
+ }
+
+ #[deriving(Clone, PartialEq)]
+ pub enum VerticalDirection {
+ Top,
+ Bottom,
+ }
+
+ fn parse_color_stop(source: ParserIter) -> Result<ColorStop,()> {
+ 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<LinearGradient,()> {
+ 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<ColorStop>,
+ }
+
+ /// 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<LengthOrPercentage>,
+ }
+
+ 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/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako
index f036003f237..b7892a9e28e 100644
--- a/components/style/properties/mod.rs.mako
+++ b/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<Url>;
- }
- 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<SpecifiedValue, ()> {
- 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<computed::Image>;
+ }
+ #[deriving(Clone)]
+ pub type SpecifiedValue = Option<common_specified::Image>;
+ #[inline]
+ pub fn get_initial_value() -> computed_value::T {
+ None
+ }
+ pub fn from_component_value(component_value: &ComponentValue, base_url: &Url)
+ -> Result<SpecifiedValue, ()> {
+ 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:single_component_value>
<%self:longhand name="background-position">
diff --git a/tests/ref/basic.list b/tests/ref/basic.list
index 1147e58b203..cc8e53e777c 100644
--- a/tests/ref/basic.list
+++ b/tests/ref/basic.list
@@ -179,3 +179,8 @@ fragment=top != ../html/acid2.html acid2_ref.html
== box_sizing_sanity_check_a.html box_sizing_sanity_check_ref.html
== inline_block_overflow_hidden_a.html inline_block_overflow_hidden_ref.html
== issue-1324.html issue-1324-ref.html
+== linear_gradients_parsing_a.html linear_gradients_parsing_ref.html
+!= linear_gradients_smoke_a.html linear_gradients_smoke_ref.html
+== linear_gradients_reverse_a.html linear_gradients_reverse_ref.html
+!= linear_gradients_corners_a.html linear_gradients_corners_ref.html
+== linear_gradients_lengths_a.html linear_gradients_lengths_ref.html
diff --git a/tests/ref/linear_gradients_corners_a.html b/tests/ref/linear_gradients_corners_a.html
new file mode 100644
index 00000000000..0a00ee6c4a1
--- /dev/null
+++ b/tests/ref/linear_gradients_corners_a.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that corners are not handled incorrectly. -->
+<style>
+section {
+ display: block;
+ width: 300px;
+ height: 150px;
+ border: solid black 1px;
+}
+#a {
+ background: linear-gradient(to top right, white, black);
+}
+</style>
+</head>
+<body>
+<section id=a></section>
+</body>
+</html>
diff --git a/tests/ref/linear_gradients_corners_ref.html b/tests/ref/linear_gradients_corners_ref.html
new file mode 100644
index 00000000000..046d39c0412
--- /dev/null
+++ b/tests/ref/linear_gradients_corners_ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that corners are not handled incorrectly. -->
+<style>
+section {
+ display: block;
+ width: 300px;
+ height: 150px;
+ border: solid black 1px;
+}
+#a {
+ background: linear-gradient(45deg, white, black);
+}
+</style>
+</head>
+<body>
+<section id=a></section>
+</body>
+</html>
diff --git a/tests/ref/linear_gradients_lengths_a.html b/tests/ref/linear_gradients_lengths_a.html
new file mode 100644
index 00000000000..12d8fa03c15
--- /dev/null
+++ b/tests/ref/linear_gradients_lengths_a.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that linear gradient lengths work. -->
+<style>
+section {
+ display: block;
+ width: 100px;
+ height: 100px;
+ border: solid black 1px;
+}
+#a {
+ background: linear-gradient(to right, white, white 30px, black 30px, black);
+}
+</style>
+</head>
+<body>
+<section id=a></section>
+<section id=b></section>
+</body>
+</html>
+
diff --git a/tests/ref/linear_gradients_lengths_ref.html b/tests/ref/linear_gradients_lengths_ref.html
new file mode 100644
index 00000000000..3a9aec01525
--- /dev/null
+++ b/tests/ref/linear_gradients_lengths_ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that linear gradient lengths work. -->
+<style>
+section {
+ display: block;
+ width: 100px;
+ height: 100px;
+ border: solid black 1px;
+}
+#a {
+ background: linear-gradient(to right, white, white 30%, black 30%, black);
+}
+</style>
+</head>
+<body>
+<section id=a></section>
+<section id=b></section>
+</body>
+</html>
+
diff --git a/tests/ref/linear_gradients_parsing_a.html b/tests/ref/linear_gradients_parsing_a.html
new file mode 100644
index 00000000000..1e08db47b71
--- /dev/null
+++ b/tests/ref/linear_gradients_parsing_a.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that parsing linear gradients works. -->
+<style>
+section {
+ display: block;
+ width: 100px;
+ height: 100px;
+ border: solid black 1px;
+}
+#a {
+ background: linear-gradient(to left, red, red);
+}
+#b {
+ background: linear-gradient(#abacab, #abacab);
+}
+#c {
+ background: linear-gradient(90deg, violet, violet 1em, violet 2ex, violet 50%, blue 50%, blue, blue);
+}
+</style>
+</head>
+<body>
+<section id=a></section>
+<section id=b></section>
+<section id=c></section>
+</body>
+</html>
diff --git a/tests/ref/linear_gradients_parsing_ref.html b/tests/ref/linear_gradients_parsing_ref.html
new file mode 100644
index 00000000000..0ce8521cf44
--- /dev/null
+++ b/tests/ref/linear_gradients_parsing_ref.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that parsing linear gradients works. -->
+<style>
+section {
+ display: block;
+ width: 100px;
+ height: 100px;
+ border: solid black 1px;
+ position: relative;
+}
+#a {
+ background: red;
+}
+#b {
+ background: #abacab;
+}
+nav {
+ display: block;
+ width: 50%;
+ height: 100%;
+ position: absolute;
+}
+#ca {
+ background: violet;
+ left: 0;
+}
+#cb {
+ background: blue;
+ right: 0;
+}
+</style>
+</head>
+<body>
+<section id=a></section>
+<section id=b></section>
+<section id=c>
+ <nav id=ca></nav>
+ <nav id=cb></nav>
+</section>
+</body>
+</html>
diff --git a/tests/ref/linear_gradients_reverse_a.html b/tests/ref/linear_gradients_reverse_a.html
new file mode 100644
index 00000000000..4172834ec37
--- /dev/null
+++ b/tests/ref/linear_gradients_reverse_a.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that reversed linear gradients are equivalent. -->
+<style>
+section {
+ display: block;
+ width: 100px;
+ height: 100px;
+ border: solid black 1px;
+}
+#a {
+ background: linear-gradient(to bottom, red, red 50%, green 50%, green);
+}
+#b {
+ background: linear-gradient(90deg, black, white);
+}
+#c {
+ background: linear-gradient(45deg, yellow, yellow 50%, purple 50%, purple);
+}
+#d {
+ background: linear-gradient(to bottom right, lime, lime 50%, pink 50%, pink);
+}
+</style>
+</head>
+<body>
+<section id=a></section>
+<section id=b></section>
+<section id=c></section>
+<section id=d></section>
+</body>
+</html>
diff --git a/tests/ref/linear_gradients_reverse_ref.html b/tests/ref/linear_gradients_reverse_ref.html
new file mode 100644
index 00000000000..eb34e7eb349
--- /dev/null
+++ b/tests/ref/linear_gradients_reverse_ref.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that reversed linear gradients are equivalent. -->
+<style>
+nav {
+ display: block;
+ width: 100px;
+ height: 100px;
+ border: solid black 1px;
+}
+#a {
+ background: linear-gradient(0deg, green, #008000 50%, red 50%, red);
+}
+#b {
+ background: linear-gradient(to left, #ffffff, black);
+}
+#c {
+ background: linear-gradient(225deg, purple, purple 50%, yellow 50%, yellow);
+}
+#d {
+ background: linear-gradient(315deg, pink, pink 50%, lime 50%, lime);
+}
+</style>
+</head>
+<body>
+<nav id=a></nav>
+<nav id=b></nav>
+<nav id=c></nav>
+<nav id=d></nav>
+</body>
+</html>
+
diff --git a/tests/ref/linear_gradients_smoke_a.html b/tests/ref/linear_gradients_smoke_a.html
new file mode 100644
index 00000000000..16c423595e5
--- /dev/null
+++ b/tests/ref/linear_gradients_smoke_a.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that linear gradients render *something*. -->
+<style>
+section {
+ display: block;
+ width: 300px;
+ height: 150px;
+ border: solid black 1px;
+}
+#a {
+ background: linear-gradient(to bottom, white, black);
+}
+#b {
+ background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet);
+}
+</style>
+</head>
+<body>
+<section id=a></section>
+<section id=b></section>
+</body>
+</html>
+
+
diff --git a/tests/ref/linear_gradients_smoke_ref.html b/tests/ref/linear_gradients_smoke_ref.html
new file mode 100644
index 00000000000..0c2a5644e19
--- /dev/null
+++ b/tests/ref/linear_gradients_smoke_ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that linear gradients render *something*. -->
+<style>
+section {
+ display: block;
+ width: 300px;
+ height: 150px;
+ border: solid black 1px;
+}
+</style>
+</head>
+<body>
+<section id=a></section>
+<section id=b></section>
+</body>
+</html>
+
+