diff --git a/frameworks/compass/stylesheets/compass/css3/_gradient.scss b/frameworks/compass/stylesheets/compass/css3/_gradient.scss index fdf0b965..31a00482 100644 --- a/frameworks/compass/stylesheets/compass/css3/_gradient.scss +++ b/frameworks/compass/stylesheets/compass/css3/_gradient.scss @@ -1,6 +1,24 @@ @import "shared"; @import "compass/utilities/general/hacks"; +@mixin background-image( + $image-1, $image-2: false, $image-3: false, + $image-4: false, $image-5: false, $image-6: false, + $image-7: false, $image-8: false, $image-9: false, $image-10: false +) { + $images: compact($image-1, $image-2, $image-3, $image-4 ,$image-5, $image-6, $image-7, $image-8, $image-9, $image-10); + @if $experimental-support-for-webkit and prefixed(-webkit, $images) { + background-image: -webkit($images); + } + @if $experimental-support-for-mozilla and prefixed(-moz, $images) { + background-image: -moz($images); + } + @if $experimental-support-for-svg and prefixed(-svg, $images) { + background-image: -svg($images); + } + background-image: $images; +} + // The linear gradient mixin works best across browsers if you use percentage-based color stops. // // Examples: @@ -50,7 +68,7 @@ @if $experimental-support-for-mozilla { background-image: #{$background}-moz-linear-gradient($start, $color-stops); } - background-image: #{$background}linear-gradient($start, $color-stops); + background-image: #{$background}#{linear}-gradient($start, $color-stops); } // Emit a IE-Specific filters that renders a simple linear gradient. @@ -104,5 +122,5 @@ @if $experimental-support-for-mozilla { background-image: #{$background}-moz-radial-gradient($center-position, circle, $color-stops); } - background-image: #{$background}radial-gradient($center-position, circle, $color-stops); + background-image: #{$background}#{radial}-gradient($center-position, circle, $color-stops); } diff --git a/lib/compass/sass_extensions/functions.rb b/lib/compass/sass_extensions/functions.rb index b8e566e6..82cf9f52 100644 --- a/lib/compass/sass_extensions/functions.rb +++ b/lib/compass/sass_extensions/functions.rb @@ -3,8 +3,8 @@ end %w( selectors enumerate urls display if - inline_image image_size gradient_support - font_files constants lists colors trig + inline_image image_size constants gradient_support + font_files lists colors trig ).each do |func| require "compass/sass_extensions/functions/#{func}" end diff --git a/lib/compass/sass_extensions/functions/gradient_support.rb b/lib/compass/sass_extensions/functions/gradient_support.rb index 445faeff..91958a07 100644 --- a/lib/compass/sass_extensions/functions/gradient_support.rb +++ b/lib/compass/sass_extensions/functions/gradient_support.rb @@ -9,13 +9,16 @@ module Compass::SassExtensions::Functions::GradientSupport values.map{|v| v.inspect}.join(", ") end def to_s - inspect + values.map{|v| v.to_s}.join(", ") 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}" + end self.color, self.stop = color, stop end def inspect @@ -35,7 +38,103 @@ module Compass::SassExtensions::Functions::GradientSupport end end + class RadialGradient < Sass::Script::Literal + attr_accessor :position_or_angle, :shape_or_size, :color_stops + def initialize(position_or_angle, shape_or_size, color_stops) + unless color_stops.values.size >= 2 + raise Sass::SyntaxError, "At least two color stops are required for a radial-gradient" + end + self.position_or_angle = position_or_angle + self.shape_or_size = shape_or_size + self.color_stops = color_stops + end + def inspect + to_s + end + def to_s + s = "radial-gradient(" + s << position_or_angle.to_s << ", " if position_or_angle + s << shape_or_size.to_s << ", " if shape_or_size + s << color_stops.to_s + s << ")" + end + def to_webkit + args = [ + grad_point(position_or_angle), + "0", + grad_point(position_or_angle), + 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_or_angle) + end + end + + class LinearGradient < Sass::Script::Literal + attr_accessor :color_stops, :position_or_angle + def initialize(position_or_angle, color_stops) + unless color_stops.values.size >= 2 + raise Sass::SyntaxError, "At least two color stops are required for a linear-gradient" + end + self.position_or_angle = position_or_angle + self.color_stops = color_stops + end + def inspect + to_s + end + def to_s + s = "linear-gradient(" + s << position_or_angle.to_s << ", " if position_or_angle + s << color_stops.to_s + s << ")" + end + def to_webkit + args = [ + grad_point(position_or_angle), + grad_point(opposite_position(position_or_angle)), + 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_or_angle) + end + end + + module Functions + + def radial_gradient(position_or_angle, shape_or_size, *color_stops) + if color_stop?(shape_or_size) + color_stops.unshift(shape_or_size) + shape_or_size = nil + end + if color_stop?(position_or_angle) + color_stops.unshift(position_or_angle) + position_or_angle = nil + end + RadialGradient.new(position_or_angle, shape_or_size, send(:color_stops, *color_stops)) + end + + def linear_gradient(position_or_angle, *color_stops) + if color_stop?(position_or_angle) + color_stops.unshift(position_or_angle) + position_or_angle = nil + end + LinearGradient.new(position_or_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| @@ -132,24 +231,7 @@ module Compass::SassExtensions::Functions::GradientSupport when Sass::Script::String # We get a string as the result of concatenation # So we have to reparse the expression - color = stop = nil - expr = Sass::Script::Parser.parse(arg.value, 0, 0) - case expr - when Sass::Script::Color - color = expr - when Sass::Script::Funcall - color = expr - when Sass::Script::Operation - unless expr.instance_variable_get("@operator") == :concat - # This should never happen. - raise Sass::SyntaxError, "Couldn't parse a color stop from: #{arg.value}" - end - color = expr.instance_variable_get("@operand1") - stop = expr.instance_variable_get("@operand2") - else - raise Sass::SyntaxError, "Couldn't parse a color stop from: #{arg.value}" - end - ColorStop.new(color, stop) + parse_color_stop(arg) else raise Sass::SyntaxError, "Not a valid color stop: #{arg}" end @@ -174,6 +256,26 @@ module Compass::SassExtensions::Functions::GradientSupport 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}) + end + + %w(webkit moz o ms svg).each do |prefix| + class_eval <<-RUBY + def _#{prefix}(*args) + args.map!{|a| a.is_a?(List) ? a.values : a}.flatten! + List.new(*args.map!{|a| a.respond_to?(:to_#{prefix}) ? a.to_#{prefix} : a}) + end + RUBY + end + + 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 + private def normalize_stops!(color_list) positions = color_list.values @@ -208,11 +310,41 @@ module Compass::SassExtensions::Functions::GradientSupport 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( ?, ...)") end + def parse_color_stop(arg) + return ColorStop.new(arg) if arg.is_a?(Sass::Script::Color) + return nil unless arg.is_a?(Sass::Script::String) + color = stop = nil + expr = Sass::Script::Parser.parse(arg.value, 0, 0) + case expr + when Sass::Script::Color + color = expr + when Sass::Script::Funcall + color = expr + when Sass::Script::Operation + unless [:concat, :space].include?(expr.instance_variable_get("@operator")) + # This should never happen. + raise Sass::SyntaxError, "Couldn't parse a color stop from: #{arg.value}" + end + color = expr.instance_variable_get("@operand1") + stop = expr.instance_variable_get("@operand2") + else + raise Sass::SyntaxError, "Couldn't parse a color stop from: #{arg.value}" + end + ColorStop.new(color, stop) + end + + def color_stop?(arg) + parse_color_stop(arg) + rescue + nil + end + def linear_svg(color_stops, x1, y1, x2, y2) gradient = <<-EOG @@ -248,4 +380,14 @@ module Compass::SassExtensions::Functions::GradientSupport EOS end end + class LinearGradient < Sass::Script::Literal + include Functions + include Compass::SassExtensions::Functions::Constants + include Compass::SassExtensions::Functions::InlineImage + end + class RadialGradient < Sass::Script::Literal + include Functions + include Compass::SassExtensions::Functions::Constants + include Compass::SassExtensions::Functions::InlineImage + end end diff --git a/test/compass_test.rb b/test/compass_test.rb index 276e527a..e79ed4d8 100644 --- a/test/compass_test.rb +++ b/test/compass_test.rb @@ -1,6 +1,8 @@ require 'test_helper' require 'fileutils' require 'compass' +require 'compass/logger' +require 'sass/plugin' class CompassTest < Test::Unit::TestCase include Compass::TestCaseHelper diff --git a/test/fixtures/stylesheets/compass/css/gradients.css b/test/fixtures/stylesheets/compass/css/gradients.css index a3bcdd3d..4d03dc0e 100644 --- a/test/fixtures/stylesheets/compass/css/gradients.css +++ b/test/fixtures/stylesheets/compass/css/gradients.css @@ -1,3 +1,24 @@ +.bg-simple-image { + background-image: url("foo.png"); } + +.bg-linear-gradient { + background-image: -webkit-gradient(linear, 0% 0%, 100% 100%, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-linear-gradient(top left, #dddddd 0%, #aaaaaa 100%); + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+IDxkZWZzPiA8bGluZWFyR3JhZGllbnQgaWQ9ImdyYWQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPiA8c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjZGRkZGRkIiAvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2FhYWFhYSIgLz4gPC9saW5lYXJHcmFkaWVudD4gPC9kZWZzPiA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPiA8L3N2Zz4g'); + background-image: linear-gradient(top left, #dddddd 0%, #aaaaaa 100%); } + +.bg-radial-gradient { + background-image: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 100, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-radial-gradient(center center, #dddddd 0%, #aaaaaa 100px); + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+IDxkZWZzPiA8cmFkaWFsR3JhZGllbnQgaWQ9ImdyYWQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjeD0iNTAlIiBjeT0iNTAlIiByPSIxMDAiPiA8c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjZGRkZGRkIiAvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2FhYWFhYSIgLz4gPC9yYWRpYWxHcmFkaWVudD4gPC9kZWZzPiA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPiA8L3N2Zz4g'); + background-image: radial-gradient(center center, #dddddd 0%, #aaaaaa 100px); } + +.bg-all-gradient-types { + background-image: url('/images/4x6.png?busted=true'), -webkit-gradient(linear, 0% 0%, 100% 100%, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)), -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 100, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: url('/images/4x6.png?busted=true'), -moz-linear-gradient(top left, #dddddd 0%, #aaaaaa 100%), -moz-radial-gradient(center center, #dddddd 0%, #aaaaaa 100px); + background-image: url('/images/4x6.png?busted=true'), url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+IDxkZWZzPiA8bGluZWFyR3JhZGllbnQgaWQ9ImdyYWQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPiA8c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjZGRkZGRkIiAvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2FhYWFhYSIgLz4gPC9saW5lYXJHcmFkaWVudD4gPC9kZWZzPiA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPiA8L3N2Zz4g'), url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+IDxkZWZzPiA8cmFkaWFsR3JhZGllbnQgaWQ9ImdyYWQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjeD0iNTAlIiBjeT0iNTAlIiByPSIxMDAiPiA8c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjZGRkZGRkIiAvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2FhYWFhYSIgLz4gPC9yYWRpYWxHcmFkaWVudD4gPC9kZWZzPiA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPiA8L3N2Zz4g'); + background-image: url('/images/4x6.png?busted=true'), linear-gradient(top left, #dddddd 0%, #aaaaaa 100%), radial-gradient(center center, #dddddd 0%, #aaaaaa 100px); } + .linear-1 { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); background-image: -moz-linear-gradient(top, #dddddd 0%, #aaaaaa 100%); diff --git a/test/fixtures/stylesheets/compass/sass/gradients.sass b/test/fixtures/stylesheets/compass/sass/gradients.sass index 399a7860..c05a4e98 100644 --- a/test/fixtures/stylesheets/compass/sass/gradients.sass +++ b/test/fixtures/stylesheets/compass/sass/gradients.sass @@ -1,5 +1,18 @@ @import compass/css3 +$experimental-support-for-svg: true +.bg-simple-image + +background-image(url('foo.png')) + +.bg-linear-gradient + +background-image(linear-gradient(top left, #ddd, #aaa)) + +.bg-radial-gradient + +background-image(radial-gradient(center center, #ddd, #aaa 100px)) + +.bg-all-gradient-types + +background-image(image-url("4x6.png"), linear-gradient(top left, #ddd, #aaa), radial-gradient(center center, #ddd, #aaa 100px)) +$experimental-support-for-svg: false .linear-1 +linear-gradient(color-stops(#dddddd, #aaaaaa))