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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+