mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +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
|
@ -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 {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
20
tests/ref/linear_gradients_corners_a.html
Normal file
20
tests/ref/linear_gradients_corners_a.html
Normal 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>
|
20
tests/ref/linear_gradients_corners_ref.html
Normal file
20
tests/ref/linear_gradients_corners_ref.html
Normal 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>
|
22
tests/ref/linear_gradients_lengths_a.html
Normal file
22
tests/ref/linear_gradients_lengths_a.html
Normal 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>
|
||||
|
22
tests/ref/linear_gradients_lengths_ref.html
Normal file
22
tests/ref/linear_gradients_lengths_ref.html
Normal 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>
|
||||
|
28
tests/ref/linear_gradients_parsing_a.html
Normal file
28
tests/ref/linear_gradients_parsing_a.html
Normal 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>
|
43
tests/ref/linear_gradients_parsing_ref.html
Normal file
43
tests/ref/linear_gradients_parsing_ref.html
Normal 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>
|
32
tests/ref/linear_gradients_reverse_a.html
Normal file
32
tests/ref/linear_gradients_reverse_a.html
Normal 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>
|
33
tests/ref/linear_gradients_reverse_ref.html
Normal file
33
tests/ref/linear_gradients_reverse_ref.html
Normal 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>
|
||||
|
26
tests/ref/linear_gradients_smoke_a.html
Normal file
26
tests/ref/linear_gradients_smoke_a.html
Normal 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>
|
||||
|
||||
|
20
tests/ref/linear_gradients_smoke_ref.html
Normal file
20
tests/ref/linear_gradients_smoke_ref.html
Normal 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>
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue