diff --git a/lib/compass/sass_extensions/functions/constants.rb b/lib/compass/sass_extensions/functions/constants.rb index ae8ae329..846787f6 100644 --- a/lib/compass/sass_extensions/functions/constants.rb +++ b/lib/compass/sass_extensions/functions/constants.rb @@ -1,17 +1,58 @@ module Compass::SassExtensions::Functions::Constants - # returns the opposite position of a side or corner. - def opposite_position(position) - opposite = position.value.split(/ +/).map do |pos| - case pos - when "top" then "bottom" - when "bottom" then "top" - when "left" then "right" - when "right" then "left" - when "center" then "center" + if defined?(Sass::Script::List) + # returns the opposite position of a side or corner. + def opposite_position(position) + position = unless position.is_a?(Sass::Script::List) + Sass::Script::List.new([position], :space) else - pos + Sass::Script::List.new(position.value.dup, position.separator) + end + position.value.map! do |pos| + if pos.is_a? Sass::Script::String + opposite = case pos.value + when "top" then "bottom" + when "bottom" then "top" + when "left" then "right" + when "right" then "left" + when "center" then "center" + else + Compass::Util.compass_warn("Cannot determine the opposite position of: #{pos}") + pos.value + end + Sass::Script::String.new(opposite) + elsif pos.is_a? Sass::Script::Number + if pos.numerator_units == ["%"] && pos.denominator_units == [] + Sass::Script::Number.new(100-pos.value, ["%"]) + else + Compass::Util.compass_warn("Cannot determine the opposite position of: #{pos}") + pos + end + else + Compass::Util.compass_warn("Cannot determine the opposite position of: #{pos}") + pos + end + end + if position.value.size == 1 + position.value.first + else + position end end - Sass::Script::String.new(opposite.join(" "), position.type) + else + # returns the opposite position of a side or corner. + def opposite_position(position) + opposite = position.value.split(/ +/).map do |pos| + case pos + when "top" then "bottom" + when "bottom" then "top" + when "left" then "right" + when "right" then "left" + when "center" then "center" + else + pos + end + end + Sass::Script::String.new(opposite.join(" "), position.type) + end end end diff --git a/lib/compass/sass_extensions/functions/gradient_support.rb b/lib/compass/sass_extensions/functions/gradient_support.rb index 9df471ea..53b067e9 100644 --- a/lib/compass/sass_extensions/functions/gradient_support.rb +++ b/lib/compass/sass_extensions/functions/gradient_support.rb @@ -1,246 +1,41 @@ module Compass::SassExtensions::Functions::GradientSupport - class List < Sass::Script::Literal - attr_accessor :values - def initialize(*values) - self.values = values - end - def join_with - ", " - end - def inspect - to_s - end - def to_s - values.map {|v| v.to_s }.join(join_with) - end - def size - values.size - end - end - - class SpaceList < List - def join_with - " " - end - end - - class ColorStop < Sass::Script::Literal - attr_accessor :color, :stop - def initialize(color, stop = nil) - unless Sass::Script::Color === color || Sass::Script::Funcall === color - raise Sass::SyntaxError, "Expected a color. Got: #{color}" + module ListFreeSassSupport + class List < Sass::Script::Literal + attr_accessor :values + def children + values end - self.color, self.stop = color, stop - end - def inspect - to_s - end - def to_s - s = color.inspect.dup - if stop - s << " " - if stop.unitless? - s << stop.times(Sass::Script::Number.new(100, ["%"])).inspect - else - s << stop.inspect - end + def value + # duck type to a Sass List + values end - s - end - end - - class RadialGradient < Sass::Script::Literal - attr_accessor :position_and_angle, :shape_and_size, :color_stops - def initialize(position_and_angle, shape_and_size, color_stops) - unless color_stops.values.size >= 2 - raise Sass::SyntaxError, "At least two color stops are required for a radial-gradient" + def initialize(*values) + self.values = values end - self.position_and_angle = position_and_angle - self.shape_and_size = shape_and_size - self.color_stops = color_stops - end - def inspect - to_s - end - def to_s - s = "radial-gradient(" - s << position_and_angle.to_s << ", " if position_and_angle - s << shape_and_size.to_s << ", " if shape_and_size - s << color_stops.to_s - s << ")" - end - def to_webkit - args = [ - grad_point(position_and_angle || Sass::Script::String.new("center center")), - "0", - grad_point(position_and_angle || Sass::Script::String.new("center center")), - grad_end_position(color_stops, Sass::Script::Bool.new(true)), - grad_color_stops(color_stops) - ] - Sass::Script::String.new("-webkit-gradient(radial, #{args.join(', ')})") - - end - def to_moz - Sass::Script::String.new("-moz-#{to_s}") - end - def to_svg - # XXX Add shape support if possible - radial_svg_gradient(color_stops, position_and_angle || Sass::Script::String.new("center center")) - end - def to_pie - Compass::Logger.new.record(:warning, "PIE does not support radial-gradient.") - Sass::Script::String.new("-pie-radial-gradient(unsupported)") - end - end - - class LinearGradient < Sass::Script::Literal - attr_accessor :color_stops, :position_and_angle - def initialize(position_and_angle, color_stops) - unless color_stops.values.size >= 2 - raise Sass::SyntaxError, "At least two color stops are required for a linear-gradient" + def join_with + ", " end - self.position_and_angle = position_and_angle - self.color_stops = color_stops - end - def inspect - to_s - end - def to_s - s = "linear-gradient(" - s << position_and_angle.to_s << ", " if position_and_angle - s << color_stops.to_s - s << ")" - end - def to_webkit - args = [] - args << grad_point(position_and_angle || Sass::Script::String.new("top")) - args << grad_point(opposite_position(position_and_angle || Sass::Script::String.new("top"))) - args << grad_color_stops(color_stops) - Sass::Script::String.new("-webkit-gradient(linear, #{args.join(', ')})") - end - def to_moz - Sass::Script::String.new("-moz-#{to_s}") - end - def to_svg - linear_svg_gradient(color_stops, position_and_angle || Sass::Script::String.new("top")) - end - def to_pie - # PIE just uses the standard rep, but the property is prefixed so - # the presence of this attribute helps flag when to render a special rule. - to_s - end - end - - module Functions - - def radial_gradient(position_and_angle, shape_and_size, *color_stops) - # Have to deal with variable length/meaning arguments. - if color_stop?(shape_and_size) - color_stops.unshift(shape_and_size) - shape_and_size = nil - elsif list_of_color_stops?(shape_and_size) - # Support legacy use of the color-stops() function - color_stops = shape_and_size.values + color_stops - shape_and_size = nil + def inspect + to_s end - shape_and_size = nil if shape_and_size && !shape_and_size.to_bool # nil out explictly passed falses - # ditto for position_and_angle - if color_stop?(position_and_angle) - color_stops.unshift(position_and_angle) - position_and_angle = nil - elsif list_of_color_stops?(position_and_angle) - color_stops = position_and_angle.values + color_stops - position_and_angle = nil + def to_s(options = self.options) + values.map {|v| v.to_s }.join(join_with) end - position_and_angle = nil if position_and_angle && !position_and_angle.to_bool - - # Support legacy use of the color-stops() function - if color_stops.size == 1 && list_of_color_stops?(color_stops.first) - color_stops = color_stops.first.values - end - RadialGradient.new(position_and_angle, shape_and_size, send(:color_stops, *color_stops)) - end - - def linear_gradient(position_and_angle, *color_stops) - if color_stop?(position_and_angle) - color_stops.unshift(position_and_angle) - position_and_angle = nil - elsif list_of_color_stops?(position_and_angle) - color_stops = position_and_angle.values + color_stops - position_and_angle = nil - end - position_and_angle = nil if position_and_angle && !position_and_angle.to_bool - - # Support legacy use of the color-stops() function - if color_stops.size == 1 && list_of_color_stops?(color_stops.first) - color_stops = color_stops.first.values - end - LinearGradient.new(position_and_angle, send(:color_stops, *color_stops)) - end - - # returns color-stop() calls for use in webkit. - def grad_color_stops(color_list) - stops = color_stops_in_percentages(color_list).map do |stop, color| - "color-stop(#{stop.inspect}, #{color.inspect})" - end - Sass::Script::String.new(stops.join(", ")) - end - - def color_stops_in_percentages(color_list) - assert_list(color_list) - color_list = normalize_stops(color_list) - max = color_list.values.last.stop - last_value = nil - color_stops = color_list.values.map do |pos| - # have to convert absolute units to percentages for use in color stop functions. - stop = pos.stop - stop = stop.div(max).times(Sass::Script::Number.new(100,["%"])) if stop.numerator_units == max.numerator_units && max.numerator_units != ["%"] - # Make sure the color stops are specified in the right order. - if last_value && last_value.value > stop.value - raise Sass::SyntaxError.new("Color stops must be specified in increasing order") - end - last_value = stop - [stop, pos.color] + def size + values.size end end - # returns the end position of the gradient from the color stop - def grad_end_position(color_list, radial = Sass::Script::Bool.new(false)) - assert_list(color_list) - default = Sass::Script::Number.new(100) - grad_position(color_list, Sass::Script::Number.new(color_list.values.size), default, radial) - end - - def grad_position(color_list, index, default, radial = Sass::Script::Bool.new(false)) - assert_list(color_list) - stop = color_list.values[index.value - 1].stop - if stop && radial.to_bool - orig_stop = stop - if stop.unitless? - if stop.value <= 1 - # A unitless number is assumed to be a percentage when it's between 0 and 1 - stop = stop.times(Sass::Script::Number.new(100, ["%"])) - else - # Otherwise, a unitless number is assumed to be in pixels - stop = stop.times(Sass::Script::Number.new(1, ["px"])) - end - end - if stop.numerator_units == ["%"] && color_list.values.last.stop && color_list.values.last.stop.numerator_units == ["px"] - stop = stop.times(color_list.values.last.stop).div(Sass::Script::Number.new(100, ["%"])) - end - Compass::Logger.new.record(:warning, "Webkit only supports pixels for the start and end stops for radial gradients. Got: #{orig_stop}") if stop.numerator_units != ["px"] - stop.div(Sass::Script::Number.new(1, stop.numerator_units, stop.denominator_units)) - elsif stop - stop - else - default + class SpaceList < List + def join_with + " " end end - # the given a position, return a point in percents + # given a position list, return a corresponding position in percents def grad_point(position) - position = position.value + position = position.is_a?(Sass::Script::String) ? position.value : position.to_s position = if position[" "] if position =~ /(top|bottom|center) (left|right|center)/ "#{$2} #{$1}" @@ -256,15 +51,16 @@ module Compass::SassExtensions::Functions::GradientSupport when /center/ "center center" else - position + "#{position} center" end end - Sass::Script::String.new(position. + position = position. gsub(/top/, "0%"). gsub(/bottom/, "100%"). gsub(/left/,"0%"). gsub(/right/,"100%"). - gsub(/center/, "50%")) + gsub(/center/, "50%") + SpaceList.new(*position.split(/ /).map{|s| Sass::Script::String.new(s)}) end def color_stops(*args) @@ -284,24 +80,6 @@ module Compass::SassExtensions::Functions::GradientSupport end) end - def linear_svg_gradient(color_stops, start) - x1, y1 = grad_point(start).to_s.split - x2, y2 = grad_point(opposite_position(start)).to_s.split - stops = color_stops_in_percentages(color_stops) - - svg = linear_svg(stops, x1, y1, x2, y2) - inline_image_string(svg.gsub(/\s+/, ' '), 'image/svg+xml') - end - - def radial_svg_gradient(color_stops, center) - cx, cy = grad_point(center).to_s.split - r = grad_end_position(color_stops, Sass::Script::Bool.new(true)) - stops = color_stops_in_percentages(color_stops) - - svg = radial_svg(stops, cx, cy, r) - inline_image_string(svg.gsub(/\s+/, ' '), 'image/svg+xml') - end - # Returns a comma-delimited list after removing any non-true values def compact(*args) List.new(*args.reject{|a| !a.to_bool}) @@ -329,21 +107,6 @@ module Compass::SassExtensions::Functions::GradientSupport SpaceList.new(*values) end - def _compass_list_size(list) - Sass::Script::Number.new(list.size) - end - - # Get the nth value from a list - def _compass_nth(list, place) - if place.value == "last" - list.values.last - elsif place.value == "first" - list.values.first - else - list.values[place.value - 1] - end - end - def _compass_space_list(list) if list.is_a?(List) && !list.is_a?(SpaceList) SpaceList.new(*list.values) @@ -354,6 +117,10 @@ module Compass::SassExtensions::Functions::GradientSupport end end + def _compass_list_size(list) + Sass::Script::Number.new(list.size) + end + # slice a sublist from a list def _compass_slice(list, start_index, end_index = nil) end_index ||= Sass::Script::Number.new(-1) @@ -364,6 +131,13 @@ module Compass::SassExtensions::Functions::GradientSupport list.class.new *list.values[start_index..end_index] end + # Check if any of the arguments passed have a tendency towards vendor prefixing. + def prefixed(prefix, *args) + method = prefix.value.sub(/^-/,"to_").to_sym + args.map!{|a| a.is_a?(List) ? a.values : a}.flatten! + Sass::Script::Bool.new(args.any?{|a| a.respond_to?(method)}) + end + %w(webkit moz o ms svg pie).each do |prefix| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def _#{prefix}(*args) @@ -378,14 +152,419 @@ module Compass::SassExtensions::Functions::GradientSupport RUBY end + protected + def color_stop?(arg) + parse_color_stop(arg) + rescue + nil + end + + def assert_list(value) + unless value.is_a?(List) + raise ArgumentError.new("#{value.inspect} is not a list") + end + end + + end + + module ListBasedSassSupport + # given a position list, return a corresponding position in percents + def grad_point(position) + position = unless position.is_a?(Sass::Script::List) + Sass::Script::List.new([position], :space) + else + Sass::Script::List.new(position.value.dup, position.separator) + end + position.value.reject!{|p| p.is_a?(Sass::Script::Number) && p.numerator_units.include?("deg")} + if (position.value.first.value =~ /top|bottom/) or (position.value.last.value =~ /left|right/) + # browsers are pretty forgiving of reversed positions so we are too. + position.value.reverse! + end + if position.value.size == 1 + if position.value.first.value =~ /top|bottom/ + position.value.unshift Sass::Script::String.new("center") + elsif position.value.first.value =~ /left|right/ + position.value.push Sass::Script::String.new("center") + end + end + position.value.map! do |p| + case p.value + when /top|left/ + Sass::Script::Number.new(0, ["%"]) + when /bottom|right/ + Sass::Script::Number.new(100, ["%"]) + when /center/ + Sass::Script::Number.new(50, ["%"]) + else + p + end + end + position + end + + def color_stops(*args) + Sass::Script::List.new(args.map do |arg| + case arg + when ColorStop + arg + when Sass::Script::Color + ColorStop.new(arg) + when Sass::Script::List + ColorStop.new(*arg.value) + else + raise Sass::SyntaxError, "Not a valid color stop: #{arg.class.name}: #{arg}" + end + end, :comma) + end + + # Returns a comma-delimited list after removing any non-true values + def compact(*args) + Sass::Script::List.new(args.reject{|a| !a.to_bool}, :comma) + end + + # Returns a list object from a value that was passed. + # This can be used to unpack a space separated list that got turned + # into a string by sass before it was passed to a mixin. + def _compass_list(arg) + if arg.is_a?(Sass::Script::List) + Sass::Script::List.new(arg.value.dup, arg.separator) + else + Sass::Script::List.new([arg], :space) + end + end + + def _compass_space_list(list) + if list.is_a?(Sass::Script::List) + Sass::Script::List.new(list.value.dup, :space) + else + Sass::Script::List.new([list], :space) + end + end + + def _compass_list_size(list) + assert_list list + Sass::Script::Number.new(list.value.size) + end + + # slice a sublist from a list + def _compass_slice(list, start_index, end_index = nil) + end_index ||= Sass::Script::Number.new(-1) + start_index = start_index.value + end_index = end_index.value + start_index -= 1 unless start_index < 0 + end_index -= 1 unless end_index < 0 + Sass::Script::List.new list.values[start_index..end_index], list.separator + end + # Check if any of the arguments passed have a tendency towards vendor prefixing. def prefixed(prefix, *args) method = prefix.value.sub(/^-/,"to_").to_sym - args.map!{|a| a.is_a?(List) ? a.values : a}.flatten! + args.map!{|a| a.is_a?(Sass::Script::List) ? a.value : a}.flatten! Sass::Script::Bool.new(args.any?{|a| a.respond_to?(method)}) end + %w(webkit moz o ms svg pie).each do |prefix| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def _#{prefix}(*args) + Sass::Script::List.new(args.map! do |a| + a.options = options + if a.is_a?(Sass::Script::List) + Sass::Script::List.new(a.value.map do |v| + v.options = options + v.respond_to?(:to_#{prefix}) ? v.to_#{prefix} : v + end, a.separator) + else + a.respond_to?(:to_#{prefix}) ? a.to_#{prefix} : a + end + end, :comma) + end + RUBY + end + + protected + def color_stop?(arg) + arg.is_a?(ColorStop) || + (arg.is_a?(Sass::Script::List) && ColorStop.new(*arg.value)) || + ColorStop.new(arg) + rescue + nil + end + + def assert_list(value) + unless value.is_a?(Sass::Script::List) + raise ArgumentError.new("#{value.inspect} is not a list") + end + end + + end + + + class ColorStop < Sass::Script::Literal + attr_accessor :color, :stop + def children + [color, stop].compact + end + def initialize(color, stop = nil) + unless Sass::Script::Color === color || Sass::Script::Funcall === color + raise Sass::SyntaxError, "Expected a color. Got: #{color}" + end + if stop && !stop.is_a?(Sass::Script::Number) + raise Sass::SyntaxError, "Expected a number. Got: #{stop}" + end + self.color, self.stop = color, stop + end + def inspect + to_s + end + def to_s(options = self.options) + s = color.inspect.dup + if stop + s << " " + if stop.unitless? + s << stop.times(Sass::Script::Number.new(100, ["%"])).inspect + else + s << stop.inspect + end + end + s + end + end + + class RadialGradient < Sass::Script::Literal + attr_accessor :position_and_angle, :shape_and_size, :color_stops + def children + [color_stops, position_and_angle, shape_and_size].compact + end + def initialize(position_and_angle, shape_and_size, color_stops) + unless color_stops.value.size >= 2 + raise Sass::SyntaxError, "At least two color stops are required for a radial-gradient" + end + self.position_and_angle = position_and_angle + self.shape_and_size = shape_and_size + self.color_stops = color_stops + end + def inspect + to_s + end + def to_s(options = self.options) + s = "radial-gradient(" + s << position_and_angle.to_s(options) << ", " if position_and_angle + s << shape_and_size.to_s(options) << ", " if shape_and_size + s << color_stops.to_s(options) + s << ")" + end + def to_webkit(options = self.options) + args = [ + grad_point(position_and_angle || _center_position), + Sass::Script::String.new("0"), + grad_point(position_and_angle || _center_position), + grad_end_position(color_stops, Sass::Script::Bool.new(true)), + grad_color_stops(color_stops) + ] + args.each {|a| a.options = options} + Sass::Script::String.new("-webkit-gradient(radial, #{args.join(', ')})") + + end + def to_moz(options = self.options) + Sass::Script::String.new("-moz-#{to_s(options)}") + end + def to_svg(options = self.options) + # XXX Add shape support if possible + radial_svg_gradient(color_stops, position_and_angle || _center_position) + end + def to_pie(options = self.options) + Compass::Logger.new.record(:warning, "PIE does not support radial-gradient.") + Sass::Script::String.new("-pie-radial-gradient(unsupported)") + end + end + + class LinearGradient < Sass::Script::Literal + attr_accessor :color_stops, :position_and_angle + def children + [color_stops, position_and_angle].compact + end + def initialize(position_and_angle, color_stops) + unless color_stops.value.size >= 2 + raise Sass::SyntaxError, "At least two color stops are required for a linear-gradient" + end + self.position_and_angle = position_and_angle + self.color_stops = color_stops + end + def inspect + to_s + end + def to_s(options = self.options) + s = "linear-gradient(" + s << position_and_angle.to_s(options) << ", " if position_and_angle + s << color_stops.to_s(options) + s << ")" + end + def to_webkit(options = self.options) + args = [] + args << grad_point(position_and_angle || Sass::Script::String.new("top")) + args << grad_point(opposite_position(position_and_angle || Sass::Script::String.new("top"))) + args << grad_color_stops(color_stops) + args.each{|a| a.options = options} + Sass::Script::String.new("-webkit-gradient(linear, #{args.join(', ')})") + end + def to_moz(options = self.options) + Sass::Script::String.new("-moz-#{to_s(options)}") + end + def to_svg(options = self.options) + linear_svg_gradient(color_stops, position_and_angle || Sass::Script::String.new("top")) + end + def to_pie(options = self.options) + # PIE just uses the standard rep, but the property is prefixed so + # the presence of this attribute helps flag when to render a special rule. + Sass::Script::String.new to_s(options) + end + end + + module Functions + + # While supporting sass 3.1 and older versions, we need two different implementations. + if defined?(Sass::Script::List) + include ListBasedSassSupport + else + include ListFreeSassSupport + end + + def radial_gradient(position_and_angle, shape_and_size, *color_stops) + # Have to deal with variable length/meaning arguments. + if color_stop?(shape_and_size) + color_stops.unshift(shape_and_size) + shape_and_size = nil + elsif list_of_color_stops?(shape_and_size) + # Support legacy use of the color-stops() function + color_stops = shape_and_size.value + color_stops + shape_and_size = nil + end + shape_and_size = nil if shape_and_size && !shape_and_size.to_bool # nil out explictly passed falses + # ditto for position_and_angle + if color_stop?(position_and_angle) + color_stops.unshift(position_and_angle) + position_and_angle = nil + elsif list_of_color_stops?(position_and_angle) + color_stops = position_and_angle.value + color_stops + position_and_angle = nil + end + position_and_angle = nil if position_and_angle && !position_and_angle.to_bool + + # Support legacy use of the color-stops() function + if color_stops.size == 1 && list_of_color_stops?(color_stops.first) + color_stops = color_stops.first.value + end + RadialGradient.new(position_and_angle, shape_and_size, send(:color_stops, *color_stops)) + end + + def linear_gradient(position_and_angle, *color_stops) + if color_stop?(position_and_angle) + color_stops.unshift(position_and_angle) + position_and_angle = nil + elsif list_of_color_stops?(position_and_angle) + color_stops = position_and_angle.value + color_stops + position_and_angle = nil + end + position_and_angle = nil if position_and_angle && !position_and_angle.to_bool + + # Support legacy use of the color-stops() function + if color_stops.size == 1 && list_of_color_stops?(color_stops.first) + color_stops = color_stops.first.value + end + LinearGradient.new(position_and_angle, send(:color_stops, *color_stops)) + end + + # returns color-stop() calls for use in webkit. + def grad_color_stops(color_list) + stops = color_stops_in_percentages(color_list).map do |stop, color| + "color-stop(#{stop.inspect}, #{color.inspect})" + end + Sass::Script::String.new(stops.join(", ")) + end + + def color_stops_in_percentages(color_list) + assert_list(color_list) + color_list = normalize_stops(color_list) + max = color_list.value.last.stop + last_value = nil + color_stops = color_list.value.map do |pos| + # have to convert absolute units to percentages for use in color stop functions. + stop = pos.stop + stop = stop.div(max).times(Sass::Script::Number.new(100,["%"])) if stop.numerator_units == max.numerator_units && max.numerator_units != ["%"] + # Make sure the color stops are specified in the right order. + if last_value && last_value.value > stop.value + raise Sass::SyntaxError.new("Color stops must be specified in increasing order") + end + last_value = stop + [stop, pos.color] + end + end + + # returns the end position of the gradient from the color stop + def grad_end_position(color_list, radial = Sass::Script::Bool.new(false)) + assert_list(color_list) + default = Sass::Script::Number.new(100) + grad_position(color_list, Sass::Script::Number.new(color_list.value.size), default, radial) + end + + def grad_position(color_list, index, default, radial = Sass::Script::Bool.new(false)) + assert_list(color_list) + stop = color_list.value[index.value - 1].stop + if stop && radial.to_bool + orig_stop = stop + if stop.unitless? + if stop.value <= 1 + # A unitless number is assumed to be a percentage when it's between 0 and 1 + stop = stop.times(Sass::Script::Number.new(100, ["%"])) + else + # Otherwise, a unitless number is assumed to be in pixels + stop = stop.times(Sass::Script::Number.new(1, ["px"])) + end + end + if stop.numerator_units == ["%"] && color_list.value.last.stop && color_list.value.last.stop.numerator_units == ["px"] + stop = stop.times(color_list.value.last.stop).div(Sass::Script::Number.new(100, ["%"])) + end + Compass::Logger.new.record(:warning, "Webkit only supports pixels for the start and end stops for radial gradients. Got: #{orig_stop}") if stop.numerator_units != ["px"] + stop.div(Sass::Script::Number.new(1, stop.numerator_units, stop.denominator_units)) + elsif stop + stop + else + default + end + end + + def linear_svg_gradient(color_stops, start) + x1, y1 = *grad_point(start).value + x2, y2 = *grad_point(opposite_position(start)).value + stops = color_stops_in_percentages(color_stops) + + svg = linear_svg(stops, x1, y1, x2, y2) + inline_image_string(svg.gsub(/\s+/, ' '), 'image/svg+xml') + end + + def radial_svg_gradient(color_stops, center) + cx, cy = *grad_point(center).value + r = grad_end_position(color_stops, Sass::Script::Bool.new(true)) + stops = color_stops_in_percentages(color_stops) + + svg = radial_svg(stops, cx, cy, r) + inline_image_string(svg.gsub(/\s+/, ' '), 'image/svg+xml') + end + + # Get the nth value from a list + def _compass_nth(list, place) + assert_list list + if place.value == "first" + list.value.first + elsif place.value == "last" + list.value.last + else + list.value[place.value - 1] + end + end + private + # After using the sass script parser to parse a string, this reconstructs # a list from operands to the space/concat operation def extract_list_values(operation) @@ -439,12 +618,11 @@ module Compass::SassExtensions::Functions::GradientSupport positions.last.stop.eq(Sass::Script::Number.new(0, ["%"])).to_bool) raise Sass::SyntaxError.new("Color stops must be specified in increasing order") end - nil - end - - def assert_list(value) - return if value.is_a?(List) - raise ArgumentError.new("#{value.inspect} is not a list of color stops. Expected: color_stops( ?, ...)") + if defined?(Sass::Script::List) + Sass::Script::List.new(positions, color_list.separator) + else + color_list.class.new(*positions) + end end def parse_color_stop(arg) @@ -470,14 +648,8 @@ module Compass::SassExtensions::Functions::GradientSupport ColorStop.new(color, stop) end - def color_stop?(arg) - parse_color_stop(arg) - rescue - nil - end - def list_of_color_stops?(arg) - arg.is_a?(List) && arg.values.first.is_a?(ColorStop) + arg.value.is_a?(Array) && arg.value.all?{|a| a.is_a?(ColorStop)} end def linear_svg(color_stops, x1, y1, x2, y2) @@ -503,6 +675,18 @@ module Compass::SassExtensions::Functions::GradientSupport #{gradient} EOS end + + def _center_position + if defined?(Sass::Script::List) + Sass::Script::List.new([ + Sass::Script::String.new("center"), + Sass::Script::String.new("center") + ],:space) + else + Sass::Script::String.new("center center") + end + end + end class LinearGradient < Sass::Script::Literal include Functions diff --git a/lib/compass/sass_extensions/functions/lists.rb b/lib/compass/sass_extensions/functions/lists.rb index 663890b8..5b1a7e53 100644 --- a/lib/compass/sass_extensions/functions/lists.rb +++ b/lib/compass/sass_extensions/functions/lists.rb @@ -2,6 +2,8 @@ module Compass::SassExtensions::Functions::Lists def first_value_of(list) if list.is_a?(Sass::Script::String) Sass::Script::String.new(list.value.split(/\s+/).first) + elsif defined?(Sass::Script::List) && list.is_a?(Sass::Script::List) + list.value.first else list end