From ae82cdab340c5d073b255d176e389cd71d3e99de Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Thu, 27 Apr 2017 12:52:04 +0200 Subject: [PATCH] Parse interpolation hints (fixes #15166) --- components/layout/display_list_builder.rs | 20 ++++--- components/style/gecko/conversions.rs | 53 ++++++++++-------- components/style/values/computed/image.rs | 65 +++++++++++++++++++--- components/style/values/computed/mod.rs | 4 +- components/style/values/specified/image.rs | 63 ++++++++++++++++----- components/style/values/specified/mod.rs | 4 +- 6 files changed, 152 insertions(+), 57 deletions(-) diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index ed38d70a90d..2e065572615 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -58,8 +58,8 @@ use style::properties::longhands::border_image_repeat::computed_value::RepeatKey use style::properties::style_structs; use style::servo::restyle_damage::REPAINT; use style::values::{Either, RGBA, computed}; -use style::values::computed::{AngleOrCorner, Gradient, GradientKind, LengthOrPercentage}; -use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage}; +use style::values::computed::{AngleOrCorner, Gradient, GradientItem, GradientKind}; +use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto, NumberOrPercentage}; use style::values::specified::{HorizontalDirection, VerticalDirection}; use style_traits::CSSPixel; use style_traits::cursor::Cursor; @@ -948,9 +948,15 @@ impl FragmentDisplayListBuilding for Fragment { // 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 = Vec::with_capacity(gradient.stops.len()); + let stop_items = gradient.items.iter().filter_map(|item| { + match *item { + GradientItem::ColorStop(ref stop) => Some(stop), + _ => None, + } + }).collect::>(); + let mut stops = Vec::with_capacity(stop_items.len()); let mut stop_run = None; - for (i, stop) in gradient.stops.iter().enumerate() { + for (i, stop) in stop_items.iter().enumerate() { let offset = match stop.position { None => { if stop_run.is_none() { @@ -960,14 +966,14 @@ impl FragmentDisplayListBuilding for Fragment { } 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) + position_to_offset(stop_items[i - 1].position.unwrap(), length) }; let (end_index, end_offset) = - match gradient.stops[i..] + match stop_items[i..] .iter() .enumerate() .find(|&(_, ref stop)| stop.position.is_some()) { - None => (gradient.stops.len() - 1, 1.0), + None => (stop_items.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 diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index 631e5abb1eb..9f7420ecbbf 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -16,7 +16,8 @@ use gecko_bindings::structs::{nsStyleCoord_CalcValue, nsStyleImage}; use gecko_bindings::structs::{nsresult, SheetType}; use gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordDataMut}; use stylesheets::{Origin, RulesMutateError}; -use values::computed::{CalcLengthOrPercentage, Gradient, Image, LengthOrPercentage, LengthOrPercentageOrAuto}; +use values::computed::{CalcLengthOrPercentage, Gradient, GradientItem, Image}; +use values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto}; impl From for nsStyleCoord_CalcValue { fn from(other: CalcLengthOrPercentage) -> nsStyleCoord_CalcValue { @@ -160,7 +161,7 @@ impl nsStyleImage { use values::computed::LengthOrPercentageOrKeyword; use values::specified::{HorizontalDirection, SizeKeyword, VerticalDirection}; - let stop_count = gradient.stops.len(); + let stop_count = gradient.items.len(); if stop_count >= ::std::u32::MAX as usize { warn!("stylo: Prevented overflow due to too many gradient stops"); return; @@ -280,32 +281,40 @@ impl nsStyleImage { }, }; - for (index, stop) in gradient.stops.iter().enumerate() { + for (index, item) in gradient.items.iter().enumerate() { // NB: stops are guaranteed to be none in the gecko side by // default. - let mut coord: nsStyleCoord = nsStyleCoord::null(); - coord.set(stop.position); - let color = match stop.color { - CSSColor::CurrentColor => { - // TODO(emilio): gecko just stores an nscolor, - // and it doesn't seem to support currentColor - // as value in a gradient. - // - // Double-check it and either remove - // currentColor for servo or see how gecko - // handles this. - 0 - }, - CSSColor::RGBA(ref rgba) => convert_rgba_to_nscolor(rgba), - }; - let mut stop = unsafe { + let mut gecko_stop = unsafe { &mut (*gecko_gradient).mStops[index] }; + let mut coord = nsStyleCoord::null(); - stop.mColor = color; - stop.mIsInterpolationHint = false; - stop.mLocation.move_from(coord); + match *item { + GradientItem::ColorStop(ref stop) => { + gecko_stop.mColor = match stop.color { + CSSColor::CurrentColor => { + // TODO(emilio): gecko just stores an nscolor, + // and it doesn't seem to support currentColor + // as value in a gradient. + // + // Double-check it and either remove + // currentColor for servo or see how gecko + // handles this. + 0 + }, + CSSColor::RGBA(ref rgba) => convert_rgba_to_nscolor(rgba), + }; + gecko_stop.mIsInterpolationHint = false; + coord.set(stop.position); + }, + GradientItem::InterpolationHint(hint) => { + gecko_stop.mIsInterpolationHint = true; + coord.set(Some(hint)); + } + } + + gecko_stop.mLocation.move_from(coord); } unsafe { diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index f196ccd6941..4ccce1e1f8f 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -120,7 +120,7 @@ impl ToCss for Image { #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct Gradient { /// The color stops. - pub stops: Vec, + pub items: Vec, /// True if this is a repeating gradient. pub repeating: bool, /// Gradient kind can be linear or radial. @@ -149,9 +149,9 @@ impl ToCss for Gradient { try!(position.to_css(dest)); }, } - for stop in &self.stops { + for item in &self.items { try!(dest.write_str(", ")); - try!(stop.to_css(dest)); + try!(item.to_css(dest)); } try!(dest.write_str(")")); Ok(()) @@ -169,8 +169,8 @@ impl fmt::Debug for Gradient { }, } - for stop in &self.stops { - let _ = write!(f, ", {:?}", stop); + for item in &self.items { + let _ = write!(f, ", {:?}", item); } Ok(()) } @@ -182,13 +182,13 @@ impl ToComputedValue for specified::Gradient { #[inline] fn to_computed_value(&self, context: &Context) -> Gradient { let specified::Gradient { - ref stops, + ref items, repeating, ref gradient_kind, compat_mode, } = *self; Gradient { - stops: stops.iter().map(|s| s.to_computed_value(context)).collect(), + items: items.iter().map(|s| s.to_computed_value(context)).collect(), repeating: repeating, gradient_kind: gradient_kind.to_computed_value(context), compat_mode: compat_mode, @@ -198,13 +198,13 @@ impl ToComputedValue for specified::Gradient { #[inline] fn from_computed_value(computed: &Gradient) -> Self { let Gradient { - ref stops, + ref items, repeating, ref gradient_kind, compat_mode, } = *computed; specified::Gradient { - stops: stops.iter().map(ToComputedValue::from_computed_value).collect(), + items: items.iter().map(ToComputedValue::from_computed_value).collect(), repeating: repeating, gradient_kind: ToComputedValue::from_computed_value(gradient_kind), compat_mode: compat_mode, @@ -251,6 +251,53 @@ impl ToComputedValue for specified::GradientKind { } } +/// Specified values for color stops and interpolation hints. +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub enum GradientItem { + /// A color stop. + ColorStop(ColorStop), + /// An interpolation hint. + InterpolationHint(LengthOrPercentage), +} + +impl ToCss for GradientItem { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + GradientItem::ColorStop(stop) => stop.to_css(dest), + GradientItem::InterpolationHint(hint) => hint.to_css(dest), + } + } +} + +impl ToComputedValue for specified::GradientItem { + type ComputedValue = GradientItem; + + #[inline] + fn to_computed_value(&self, context: &Context) -> GradientItem { + match *self { + specified::GradientItem::ColorStop(ref stop) => { + GradientItem::ColorStop(stop.to_computed_value(context)) + }, + specified::GradientItem::InterpolationHint(ref hint) => { + GradientItem::InterpolationHint(hint.to_computed_value(context)) + }, + } + } + + #[inline] + fn from_computed_value(computed: &GradientItem) -> Self { + match *computed { + GradientItem::ColorStop(ref stop) => { + specified::GradientItem::ColorStop(ToComputedValue::from_computed_value(stop)) + }, + GradientItem::InterpolationHint(ref hint) => { + specified::GradientItem::InterpolationHint(ToComputedValue::from_computed_value(hint)) + }, + } + } +} + /// Computed values for one color stop in a linear gradient. /// https://drafts.csswg.org/css-images/#typedef-color-stop-list #[derive(Clone, PartialEq, Copy)] diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 85a8ba19368..4b4588fbe4f 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -17,8 +17,8 @@ use super::specified::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as G pub use app_units::Au; pub use cssparser::Color as CSSColor; -pub use self::image::{AngleOrCorner, EndingShape as GradientShape, Gradient, GradientKind, Image, ImageRect}; -pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword}; +pub use self::image::{AngleOrCorner, EndingShape as GradientShape, Gradient, GradientItem}; +pub use self::image::{GradientKind, Image, ImageRect, LengthOrKeyword, LengthOrPercentageOrKeyword}; pub use super::{Auto, Either, None_}; #[cfg(feature = "gecko")] pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems}; diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 501ef2948c9..5ca137864c7 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -92,8 +92,8 @@ impl Image { #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct Gradient { - /// The color stops. - pub stops: Vec, + /// The color stops and interpolation hints. + pub items: Vec, /// True if this is a repeating gradient. pub repeating: bool, /// Gradients can be linear or radial. @@ -132,13 +132,13 @@ impl ToCss for Gradient { } }, } - for stop in &self.stops { + for item in &self.items { if !skipcomma { try!(dest.write_str(", ")); } else { skipcomma = false; } - try!(stop.to_css(dest)); + try!(item.to_css(dest)); } dest.write_str(")") } @@ -148,18 +148,18 @@ impl Gradient { /// Parses a gradient from the given arguments. pub fn parse_function(context: &ParserContext, input: &mut Parser) -> Result { fn parse(context: &ParserContext, input: &mut Parser, parse_kind: F) - -> Result<(GradientKind, Vec), ()> + -> Result<(GradientKind, Vec), ()> where F: FnOnce(&ParserContext, &mut Parser) -> Result { input.parse_nested_block(|input| { let kind = try!(parse_kind(context, input)); - let stops = try!(input.parse_comma_separated(|i| ColorStop::parse(context, i))); - Ok((kind, stops)) + let items = try!(Gradient::parse_items(context, input)); + Ok((kind, items)) }) }; let mut repeating = false; let mut compat_mode = CompatMode::Modern; - let (gradient_kind, stops) = match_ignore_ascii_case! { &try!(input.expect_function()), + let (gradient_kind, items) = match_ignore_ascii_case! { &try!(input.expect_function()), "linear-gradient" => { try!(parse(context, input, GradientKind::parse_modern_linear)) }, @@ -195,18 +195,31 @@ impl Gradient { _ => { return Err(()); } }; - // https://drafts.csswg.org/css-images/#typedef-color-stop-list - if stops.len() < 2 { - return Err(()) - } - Ok(Gradient { - stops: stops, + items: items, repeating: repeating, gradient_kind: gradient_kind, compat_mode: compat_mode, }) } + + fn parse_items(context: &ParserContext, input: &mut Parser) -> Result, ()> { + let mut seen_stop = false; + let items = try!(input.parse_comma_separated(|input| { + if seen_stop { + if let Ok(hint) = input.try(|i| LengthOrPercentage::parse(context, i)) { + seen_stop = false; + return Ok(GradientItem::InterpolationHint(hint)); + } + } + seen_stop = true; + ColorStop::parse(context, input).map(GradientItem::ColorStop) + })); + if !seen_stop || items.len() < 2 { + return Err(()); + } + Ok(items) + } } /// Specified values for CSS linear or radial gradients. @@ -489,7 +502,27 @@ impl AngleOrCorner { } } -/// Specified values for one color stop in a linear gradient. +/// Specified values for color stops and interpolation hints. +/// https://drafts.csswg.org/css-images-4/#color-stop-syntax +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub enum GradientItem { + /// A color stop. + ColorStop(ColorStop), + /// An interpolation hint. + InterpolationHint(LengthOrPercentage), +} + +impl ToCss for GradientItem { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + GradientItem::ColorStop(ref stop) => stop.to_css(dest), + GradientItem::InterpolationHint(ref hint) => hint.to_css(dest), + } + } +} + +/// Specified values for one color stop in a gradient. /// https://drafts.csswg.org/css-images/#typedef-color-stop-list #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 5b7393ebea9..040728d8e77 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -29,8 +29,8 @@ pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, Justify pub use self::color::Color; pub use self::grid::{GridLine, TrackKeyword}; pub use self::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient}; -pub use self::image::{GradientKind, HorizontalDirection, Image, ImageRect, LengthOrKeyword}; -pub use self::image::{LengthOrPercentageOrKeyword, SizeKeyword, VerticalDirection}; +pub use self::image::{GradientItem, GradientKind, HorizontalDirection, Image, ImageRect}; +pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword, SizeKeyword, VerticalDirection}; pub use self::length::AbsoluteLength; pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage}; pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};