From 40d32606bba638fe46393cd83ea49202dc2bb6d5 Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Mon, 29 Nov 2010 20:50:50 -0800 Subject: [PATCH] Don't rely on reading the environment within the sprite code. Instead accept variable keyword args. --- .../sass_extensions/functions/sprites.rb | 40 +++-- lib/compass/sprites.rb | 145 +++++++++++------- 2 files changed, 118 insertions(+), 67 deletions(-) diff --git a/lib/compass/sass_extensions/functions/sprites.rb b/lib/compass/sass_extensions/functions/sprites.rb index 3ca2c23b..41a7e619 100644 --- a/lib/compass/sass_extensions/functions/sprites.rb +++ b/lib/compass/sass_extensions/functions/sprites.rb @@ -3,16 +3,32 @@ require 'chunky_png' module Compass::SassExtensions::Functions::Sprites SASS_NULL = Sass::Script::Number::new(0) - def generate_sprite_image(uri) + # Provides a consistent interface for getting a variable in ruby + # from a keyword argument hash that accounts for underscores/dash equivalence + # and allows the caller to pass a symbol instead of a string. + module VariableReader + def get_var(variable_name) + self[variable_name.to_s.gsub(/-/,"_")] + end + end + + def generate_sprite_image(uri, kwargs = {}) + kwargs.extend VariableReader path, name = Compass::Sprites.path_and_name(uri.value) last_spacing = 0 width = 0 height = 0 + + # Get image metadata + Compass::Sprites.discover_sprites(uri.value).each do |file| + Compass::Sprites.compute_image_metadata! file, path, name + end + images = Compass::Sprites.sprites(path, name) - + # Calculation images.each do |image| - current_spacing = number_from_var("#{name}-#{image[:name]}-spacing") + current_spacing = number_from_var(kwargs, "#{image[:name]}-spacing", 0) if height > 0 height += [current_spacing, last_spacing].max end @@ -27,14 +43,18 @@ module Compass::SassExtensions::Functions::Sprites images.each do |image| input_png = ChunkyPNG::Image.from_file(image[:file]) - position = environment.var("#{name}-#{image[:name]}-position") + position = kwargs.get_var("#{image[:name]}-position") || Sass::Script::Number.new(0, ["%"]) if position.unit_str == "%" image[:x] = (width - image[:width]) * (position.value / 100) else image[:x] = position.value end - repeat = environment.var("#{name}-#{image[:name]}-repeat").to_s + repeat = if (var = kwargs.get_var("#{image[:name]}-repeat")) + var.value + else + "no-repeat" + end if repeat == "no-repeat" output_png.replace input_png, image[:x], image[:y] else @@ -49,6 +69,7 @@ module Compass::SassExtensions::Functions::Sprites sprite_url(uri) end + Sass::Script::Functions.declare :generate_sprite_image, [:uri], :var_kwargs => true def sprite_image(uri, x_shift = SASS_NULL, y_shift = SASS_NULL, depricated_1 = nil, depricated_2 = nil) check_spacing_deprecation uri, depricated_1, depricated_2 @@ -80,11 +101,12 @@ module Compass::SassExtensions::Functions::Sprites private - def number_from_var(var_name) - if var = environment.var(var_name) - var.value + def number_from_var(kwargs, var_name, default_value) + if number = kwargs.get_var(var_name) + assert_type number, :Number + number.value else - 0 + default_value end end diff --git a/lib/compass/sprites.rb b/lib/compass/sprites.rb index 38aa002b..28224a70 100644 --- a/lib/compass/sprites.rb +++ b/lib/compass/sprites.rb @@ -28,6 +28,25 @@ module Compass raise Compass::Error, %Q(`@import` statement missing. Please add `@import "#{path}/*.png";`.) end end + + def discover_sprites(uri) + glob = File.join(Compass.configuration.images_path, uri) + Dir.glob(glob).sort + end + + def compute_image_metadata!(file, path, name) + width, height = Compass::SassExtensions::Functions::ImageSize::ImageProperties.new(file).size + sprite_name = File.basename(file, '.png'); + unless sprite_name =~ /\A#{Sass::SCSS::RX::IDENT}\Z/ + raise Sass::SyntaxError, "#{sprite_name} must be a legal css identifier" + end + sprites(path, name, true) << { + :name => sprite_name, + :file => file, + :height => height, + :width => width + } + end end def images @@ -37,68 +56,78 @@ module Compass def find(uri, options) if uri =~ /\.png$/ self.path, self.name = Compass::Sprites.path_and_name(uri) - glob = File.join(Compass.configuration.images_path, uri) - Dir.glob(glob).sort.each do |file| - width, height = Compass::SassExtensions::Functions::ImageSize::ImageProperties.new(file).size - images << { - :name => File.basename(file, '.png'), - :file => file, - :height => height, - :width => width - } - end - - contents = <<-SCSS - $#{name}-sprite-base-class: ".#{name}-sprite" !default; - $#{name}-sprite-dimensions: false !default; - $#{name}-position: 0% !default; - $#{name}-spacing: 0 !default; - $#{name}-repeat: no-repeat !default; - - #{images.map do |sprite| - <<-SCSS - $#{name}-#{sprite[:name]}-position: $#{name}-position !default; - $#{name}-#{sprite[:name]}-spacing: $#{name}-spacing !default; - $#{name}-#{sprite[:name]}-repeat: $#{name}-repeat !default; - SCSS - end.join} - - \#{$#{name}-sprite-base-class} { - background: generate-sprite-image("#{uri}") no-repeat; - } - - @mixin #{name}-sprite-dimensions($sprite) { - height: image-height("#{name}/\#{$sprite}.png"); - width: image-width("#{name}/\#{$sprite}.png"); - } - - @mixin #{name}-sprite-position($sprite, $x: 0, $y: 0) { - background-position: sprite-position("#{path}/\#{$sprite}.png", $x, $y); - } - - @mixin #{name}-sprite($sprite, $dimensions: $#{name}-sprite-dimensions, $x: 0, $y: 0) { - @extend \#{$#{name}-sprite-base-class}; - @include #{name}-sprite-position($sprite, $x, $y); - @if $dimensions { - @include #{name}-sprite-dimensions($sprite); - } - } - - @mixin all-#{name}-sprites { - #{images.map do |sprite| - <<-SCSS - .#{name}-#{sprite[:name]} { - @include #{name}-sprite("#{sprite[:name]}"); - } - SCSS - end.join} - } - SCSS options.merge! :filename => name, :syntax => :scss, :importer => self - Sass::Engine.new(contents, options) + image_names = Compass::Sprites.discover_sprites(uri).map{|i| File.basename(i, '.png')} + Sass::Engine.new(content_for_images(uri, name, image_names), options) end end + def content_for_images(uri, name, images) + <<-SCSS +// General Sprite Defaults +// You can override them before you import this file. +$#{name}-sprite-base-class: ".#{name}-sprite" !default; +$#{name}-sprite-dimensions: false !default; +$#{name}-position: 0% !default; +$#{name}-spacing: 0 !default; +$#{name}-repeat: no-repeat !default; + +// These variables control the generated sprite output +// You can override them selectively before you import this file. +#{images.map do |sprite_name| +<<-SCSS +$#{name}-#{sprite_name}-position: $#{name}-position !default; +$#{name}-#{sprite_name}-spacing: $#{name}-spacing !default; +$#{name}-#{sprite_name}-repeat: $#{name}-repeat !default; +SCSS +end.join} + +// All sprites should extend this class +// The #{name}-sprite mixin will do so for you. +\#{$#{name}-sprite-base-class} { + background: generate-sprite-image("#{uri}", +#{images.map do |sprite_name| +%Q{ $#{sprite_name}-position: $#{name}-#{sprite_name}-position, + $#{sprite_name}-spacing: $#{name}-#{sprite_name}-spacing, + $#{sprite_name}-repeat: $#{name}-#{sprite_name}-repeat} +end.join(",\n")}) no-repeat; +} + +// Use this to set the dimensions of an element +// based on the size of the original image. +@mixin #{name}-sprite-dimensions($sprite) { + height: image-height("#{name}/\#{$sprite}.png"); + width: image-width("#{name}/\#{$sprite}.png"); +} + +// Move the background position to display the sprite. +@mixin #{name}-sprite-position($sprite, $x: 0, $y: 0) { + background-position: sprite-position("#{path}/\#{$sprite}.png", $x, $y); +} + +// Extends the sprite base class and set the background position for the desired sprite. +// It will also apply the image dimensions if $dimensions is true. +@mixin #{name}-sprite($sprite, $dimensions: $#{name}-sprite-dimensions, $x: 0, $y: 0) { + @extend \#{$#{name}-sprite-base-class}; + @include #{name}-sprite-position($sprite, $x, $y); + @if $dimensions { + @include #{name}-sprite-dimensions($sprite); + } +} + +// Generates a class for each sprited image. +@mixin all-#{name}-sprites { +#{images.map do |sprite_name| +<<-SCSS + .#{name}-#{sprite_name} { + @include #{name}-sprite("#{sprite_name}"); + } +SCSS +end.join} +} + SCSS + end + def key(uri, options) [self.class.name + ":" + File.dirname(File.expand_path(uri)), File.basename(uri)]