layout: Implement CSS linear gradients per the CSS-IMAGES specification.

This implements the CSS `linear-gradient` property per the CSS-IMAGES
specification:

    http://dev.w3.org/csswg/css-images-3/

Improves GitHub.
This commit is contained in:
Patrick Walton 2014-10-15 21:07:36 -07:00
parent 44fa9f9b18
commit 215c2a9d4c
17 changed files with 865 additions and 42 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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