mirror of
https://github.com/servo/servo.git
synced 2025-08-12 00:45:33 +01:00
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:
parent
44fa9f9b18
commit
215c2a9d4c
17 changed files with 865 additions and 42 deletions
|
@ -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};
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue