compass 0.11.beta.3 → 0.11.beta.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ require 'digest/md5'
2
+ require 'compass/sass_extensions/sprites/sprites'
3
+ require 'compass/sass_extensions/sprites/sprite_map'
4
+ require 'compass/sass_extensions/sprites/image'
5
+ require 'compass/sass_extensions/sprites/base'
6
+ require 'compass/sass_extensions/sprites/engines'
7
+
8
+ module Compass
9
+ module SassExtensions
10
+ module Sprites
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,183 @@
1
+ module Compass
2
+ module SassExtensions
3
+ module Sprites
4
+ class Base < Sass::Script::Literal
5
+ def self.from_uri(uri, context, kwargs)
6
+ sprite_map = ::Compass::SpriteMap.new(uri.value, {})
7
+
8
+ sprites = sprite_map.files.map do |sprite|
9
+ sprite.gsub(Compass.configuration.images_path+"/", "")
10
+ end
11
+ new(sprites, sprite_map.path, sprite_map.name, context, kwargs)
12
+ end
13
+
14
+ def require_engine!
15
+ self.class.send(:include, eval("::Compass::SassExtensions::Sprites::#{modulize}Engine"))
16
+ end
17
+
18
+ # Changing this string will invalidate all previously generated sprite images.
19
+ # We should do so only when the packing algorithm changes
20
+ SPRITE_VERSION = "1"
21
+
22
+ attr_accessor :image_names, :path, :name, :options
23
+ attr_accessor :images, :width, :height
24
+
25
+
26
+ def initialize(image_names, path, name, context, options)
27
+ require_engine!
28
+ @image_names, @path, @name, @options = image_names, path, name, options
29
+ @images = nil
30
+ @width = nil
31
+ @height = nil
32
+ @evaluation_context = context
33
+ validate!
34
+ compute_image_metadata!
35
+ end
36
+
37
+ # Calculate the size of the sprite
38
+ def size
39
+ [width, height]
40
+ end
41
+
42
+ # Calculates the overal image dimensions
43
+ # collects image sizes and input parameters for each sprite
44
+ def compute_image_metadata!
45
+ @width = 0
46
+ init_images
47
+ compute_image_positions!
48
+ @height = @images.last.top + @images.last.height
49
+ end
50
+
51
+ def init_images
52
+ @images = image_names.collect do |relative_file|
53
+ image = Compass::SassExtensions::Sprites::Image.new(self, relative_file, options)
54
+ @width = [ @width, image.width + image.offset ].max
55
+ image
56
+ end
57
+ end
58
+
59
+ # Calculates the overal image dimensions
60
+ # collects image sizes and input parameters for each sprite
61
+ def compute_image_positions!
62
+ @images.each_with_index do |image, index|
63
+ image.left = image.position.unit_str == "%" ? (@width - image.width) * (image.position.value / 100) : image.position.value
64
+ next if index == 0
65
+ last_image = @images[index-1]
66
+ image.top = last_image.top + last_image.height + [image.spacing, last_image.spacing].max
67
+ end
68
+ end
69
+
70
+ def image_for(name)
71
+ @images.detect { |img| img.name == name}
72
+ end
73
+
74
+ def has_hover?(name)
75
+ !image_for("#{name}_hover").nil?
76
+ end
77
+
78
+ def has_target?(name)
79
+ !image_for("#{name}_target").nil?
80
+ end
81
+
82
+ def has_active?(name)
83
+ !image_for("#{name}_active").nil?
84
+ end
85
+
86
+ def sprite_names
87
+ image_names.map { |f| File.basename(f, '.png') }
88
+ end
89
+
90
+ def validate!
91
+ for sprite_name in sprite_names
92
+ unless sprite_name =~ /\A#{Sass::SCSS::RX::IDENT}\Z/
93
+ raise Sass::SyntaxError, "#{sprite_name} must be a legal css identifier"
94
+ end
95
+ end
96
+ end
97
+
98
+ # The on-the-disk filename of the sprite
99
+ def filename
100
+ File.join(Compass.configuration.images_path, "#{path}-#{uniqueness_hash}.png")
101
+ end
102
+
103
+ # Generate a sprite image if necessary
104
+ def generate
105
+ if generation_required?
106
+ sprite_data = construct_sprite
107
+ save!(sprite_data)
108
+ Compass.configuration.run_callback(:sprite_generated, sprite_data)
109
+ end
110
+ end
111
+
112
+ def generation_required?
113
+ !File.exists?(filename) || outdated?
114
+ end
115
+
116
+ def uniqueness_hash
117
+ @uniqueness_hash ||= begin
118
+ sum = Digest::MD5.new
119
+ sum << SPRITE_VERSION
120
+ sum << path
121
+ images.each do |image|
122
+ [:relative_file, :height, :width, :repeat, :spacing, :position, :digest].each do |attr|
123
+ sum << image.send(attr).to_s
124
+ end
125
+ end
126
+ sum.hexdigest[0...10]
127
+ end
128
+ @uniqueness_hash
129
+ end
130
+
131
+ def save!(output_png)
132
+ saved = output_png.save filename
133
+ Compass.configuration.run_callback(:sprite_saved, filename)
134
+ saved
135
+ end
136
+
137
+ # All the full-path filenames involved in this sprite
138
+ def image_filenames
139
+ @images.map(&:file)
140
+ end
141
+
142
+ # Checks whether this sprite is outdated
143
+ def outdated?
144
+ if File.exists?(filename)
145
+ return @images.map(&:mtime).any? { |mtime| mtime > self.mtime }
146
+ end
147
+ true
148
+ end
149
+
150
+ def mtime
151
+ File.mtime(filename)
152
+ end
153
+
154
+ def inspect
155
+ to_s
156
+ end
157
+
158
+ def to_s(options = self.options)
159
+ sprite_url(self).value
160
+ end
161
+
162
+ def respond_to?(meth)
163
+ super || @evaluation_context.respond_to?(meth)
164
+ end
165
+
166
+ def method_missing(meth, *args, &block)
167
+ if @evaluation_context.respond_to?(meth)
168
+ @evaluation_context.send(meth, *args, &block)
169
+ else
170
+ super
171
+ end
172
+ end
173
+
174
+ private
175
+
176
+ def modulize
177
+ @modulize ||= Compass::configuration.sprite_engine.to_s.scan(/([^_.]+)/).flatten.map {|chunk| "#{chunk[0].chr.upcase}#{chunk[1..-1]}" }.join
178
+ end
179
+
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1 @@
1
+ require 'compass/sass_extensions/sprites/engines/chunky_png_engine'
@@ -0,0 +1,33 @@
1
+ begin
2
+ require 'oily_png'
3
+ rescue LoadError
4
+ require 'chunky_png'
5
+ end
6
+
7
+ module Compass
8
+ module SassExtensions
9
+ module Sprites
10
+ module ChunkyPngEngine
11
+
12
+ # Returns a PNG object
13
+ def construct_sprite
14
+ #require_png_library!
15
+ output_png = ChunkyPNG::Image.new(width, height, ChunkyPNG::Color::TRANSPARENT)
16
+ images.each do |image|
17
+ input_png = ChunkyPNG::Image.from_file(image.file)
18
+ if image.repeat == "no-repeat"
19
+ output_png.replace! input_png, image.left, image.top
20
+ else
21
+ x = image.left - (image.left / image.width).ceil * image.width
22
+ while x < width do
23
+ output_png.replace! input_png, x, image.top
24
+ x += image.width
25
+ end
26
+ end
27
+ end
28
+ output_png
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,89 @@
1
+ module Compass
2
+ module SassExtensions
3
+ module Sprites
4
+ class Image
5
+ attr_reader :relative_file, :options, :base
6
+ attr_accessor :top, :left
7
+
8
+ def initialize(base, relative_file, options)
9
+ @base, @relative_file, @options = base, relative_file, options
10
+ @left = @top = 0
11
+ end
12
+
13
+ def file
14
+ File.join(Compass.configuration.images_path, relative_file)
15
+ end
16
+
17
+ def width
18
+ dimensions.first
19
+ end
20
+
21
+ def height
22
+ dimensions.last
23
+ end
24
+
25
+ def name
26
+ File.basename(relative_file, '.png')
27
+ end
28
+
29
+ def repeat
30
+ [ "#{name}-repeat", "repeat" ].each { |which|
31
+ if var = options.get_var(which)
32
+ return var.value
33
+ end
34
+ }
35
+ "no-repeat"
36
+ end
37
+
38
+ def position
39
+ options.get_var("#{name}-position") || options.get_var("position") || Sass::Script::Number.new(0, ["px"])
40
+ end
41
+
42
+ def offset
43
+ (position.unitless? || position.unit_str == "px") ? position.value : 0
44
+ end
45
+
46
+ def spacing
47
+ (options.get_var("#{name}-spacing") || options.get_var("spacing") || Sass::Script::Number.new(0)).value
48
+ end
49
+
50
+ def digest
51
+ Digest::MD5.file(file).hexdigest
52
+ end
53
+
54
+ def mtime
55
+ File.mtime(file)
56
+ end
57
+
58
+ def hover?
59
+ base.has_hover?(name)
60
+ end
61
+
62
+ def hover
63
+ base.image_for("#{name}_hover")
64
+ end
65
+
66
+ def target?
67
+ base.has_target?(name)
68
+ end
69
+
70
+ def target
71
+ base.image_for("#{name}_target")
72
+ end
73
+
74
+ def active?
75
+ base.has_active?(name)
76
+ end
77
+
78
+ def active
79
+ base.image_for("#{name}_active")
80
+ end
81
+
82
+ private
83
+ def dimensions
84
+ @dimensions ||= Compass::SassExtensions::Functions::ImageSize::ImageProperties.new(file).size
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,41 +1,51 @@
1
1
  module Compass
2
- class Sprites < Sass::Importers::Base
3
- attr_accessor :name
4
- attr_accessor :path
5
-
6
- class << self
7
- def path_and_name(uri)
8
- if uri =~ %r{((.+/)?(.+))/(.+?)\.png}
9
- [$1, $3, $4]
10
- end
11
- end
12
-
13
- def discover_sprites(uri)
14
- glob = File.join(Compass.configuration.images_path, uri)
15
- Dir.glob(glob).sort
16
- end
2
+ class SpriteMap
3
+ attr_reader :uri, :options
17
4
 
18
- def sprite_name(file)
19
- File.basename(file, '.png')
20
- end
5
+ def initialize(uri, options)
6
+ @uri, @options = uri, options
7
+ end
8
+
9
+ def name
10
+ ensure_path_and_name!
11
+ @name
12
+ end
21
13
 
14
+ def path
15
+ ensure_path_and_name!
16
+ @path
22
17
  end
23
18
 
24
- def find_relative(*args)
25
- nil
19
+ def files
20
+ @files ||= Dir[File.join(Compass.configuration.images_path, uri)].sort
26
21
  end
27
22
 
28
- def find(uri, options)
29
- if uri =~ /\.png$/
30
- self.path, self.name = Compass::Sprites.path_and_name(uri)
31
- options.merge! :filename => name, :syntax => :scss, :importer => self
32
- sprite_files = Compass::Sprites.discover_sprites(uri)
33
- image_names = sprite_files.map {|i| Compass::Sprites.sprite_name(i) }
34
- Sass::Engine.new(content_for_images(uri, name, image_names), options)
23
+ def sprite_names
24
+ @sprite_names ||= files.collect { |file| File.basename(file, '.png') }
25
+ end
26
+
27
+ def sass_options
28
+ @sass_options ||= options.merge(:filename => name, :syntax => :scss, :importer => self)
29
+ end
30
+
31
+ def mtime
32
+ Compass.quick_cache("mtime:#{uri}") do
33
+ files.collect { |file| File.mtime(file) }.max
35
34
  end
36
35
  end
37
36
 
38
- def content_for_images(uri, name, images, skip_overrides = false)
37
+ def sass_engine
38
+ Sass::Engine.new(content_for_images, options)
39
+ end
40
+
41
+ private
42
+ def ensure_path_and_name!
43
+ return if @path && @name
44
+ uri =~ %r{((.+/)?(.+))/(.+?)\.png}
45
+ @path, @name = $1, $3
46
+ end
47
+
48
+ def content_for_images(skip_overrides = false)
39
49
  <<-SCSS
40
50
  @import "compass/utilities/sprites/base";
41
51
 
@@ -46,8 +56,9 @@ $#{name}-sprite-dimensions: false !default;
46
56
  $#{name}-position: 0% !default;
47
57
  $#{name}-spacing: 0 !default;
48
58
  $#{name}-repeat: no-repeat !default;
59
+ $#{name}-prefix: '' !default;
49
60
 
50
- #{skip_overrides ? "$#{name}-sprites: sprite-map(\"#{uri}\");" : generate_overrides(uri, name, images) }
61
+ #{skip_overrides ? "$#{name}-sprites: sprite-map(\"#{uri}\");" : generate_overrides }
51
62
 
52
63
  // All sprites should extend this class
53
64
  // The #{name}-sprite mixin will do so for you.
@@ -79,44 +90,26 @@ $#{name}-repeat: no-repeat !default;
79
90
 
80
91
  // Generates a class for each sprited image.
81
92
  @mixin all-#{name}-sprites($dimensions: $#{name}-sprite-dimensions, $prefix: sprite-map-name($#{name}-sprites)) {
82
- @include #{name}-sprites(#{images.join(" ")}, $dimensions, $prefix);
93
+ @include #{name}-sprites(#{sprite_names.join(" ")}, $dimensions, $prefix);
83
94
  }
84
95
  SCSS
85
96
  end
86
97
 
87
- def key(uri, options)
88
- [self.class.name + ":" + File.dirname(File.expand_path(uri)),
89
- File.basename(uri)]
90
- end
91
-
92
- def mtime(uri, options)
93
- Compass.quick_cache("mtime:#{uri}") do
94
- self.path, self.name = Compass::Sprites.path_and_name(uri)
95
- glob = File.join(Compass.configuration.images_path, uri)
96
- Dir.glob(glob).inject(Time.at(0)) do |max_time, file|
97
- (t = File.mtime(file)) > max_time ? t : max_time
98
- end
99
- end
100
- end
101
-
102
- def to_s
103
- ""
104
- end
105
-
106
- def generate_overrides(uri, name,images)
98
+ def generate_overrides
107
99
  content = <<-TXT
108
100
  // These variables control the generated sprite output
109
101
  // You can override them selectively before you import this file.
110
102
  TXT
111
- images.map do |sprite_name|
103
+ sprite_names.map do |sprite_name|
112
104
  content += <<-SCSS
113
105
  $#{name}-#{sprite_name}-position: $#{name}-position !default;
114
106
  $#{name}-#{sprite_name}-spacing: $#{name}-spacing !default;
115
107
  $#{name}-#{sprite_name}-repeat: $#{name}-repeat !default;
116
108
  SCSS
117
109
  end.join
110
+
118
111
  content += "\n$#{name}-sprites: sprite-map(\"#{uri}\",\n"
119
- content += images.map do |sprite_name|
112
+ content += sprite_names.map do |sprite_name|
120
113
  %Q{ $#{sprite_name}-position: $#{name}-#{sprite_name}-position,
121
114
  $#{sprite_name}-spacing: $#{name}-#{sprite_name}-spacing,
122
115
  $#{sprite_name}-repeat: $#{name}-#{sprite_name}-repeat}
@@ -125,3 +118,4 @@ $#{name}-#{sprite_name}-repeat: $#{name}-repeat !default;
125
118
  end
126
119
  end
127
120
  end
121
+