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.
- data/VERSION.yml +1 -1
- data/features/extensions.feature +7 -0
- data/frameworks/compass/stylesheets/compass/css3/_transform.scss +4 -4
- data/lib/compass.rb +6 -3
- data/lib/compass/app_integration/rails/templates/compass-install-rails.rb +4 -4
- data/lib/compass/configuration.rb +2 -1
- data/lib/compass/configuration/defaults.rb +5 -0
- data/lib/compass/configuration/helpers.rb +14 -6
- data/lib/compass/sass_extensions.rb +1 -0
- data/lib/compass/sass_extensions/functions/sprites.rb +10 -229
- data/lib/compass/sass_extensions/sprites.rb +14 -0
- data/lib/compass/sass_extensions/sprites/base.rb +183 -0
- data/lib/compass/sass_extensions/sprites/engines.rb +1 -0
- data/lib/compass/sass_extensions/sprites/engines/chunky_png_engine.rb +33 -0
- data/lib/compass/sass_extensions/sprites/image.rb +89 -0
- data/lib/compass/{sprites.rb → sass_extensions/sprites/sprite_map.rb} +46 -52
- data/lib/compass/sass_extensions/sprites/sprites.rb +26 -0
- data/lib/compass/util.rb +11 -0
- data/test/fixtures/stylesheets/compass/css/transform.css +2 -2
- data/test/test_case_helper.rb +1 -1
- metadata +11 -8
- data/examples/compass/saved_stylesheets/PIE.htc +0 -77
- data/examples/compass/src/sprites/_emblem.scss +0 -90
- data/examples/compass/src/sprites/_flag.scss +0 -44
- data/lib/compass/core_ext.rb +0 -14
@@ -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
|
3
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
25
|
-
|
19
|
+
def files
|
20
|
+
@files ||= Dir[File.join(Compass.configuration.images_path, uri)].sort
|
26
21
|
end
|
27
22
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
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
|
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(#{
|
93
|
+
@include #{name}-sprites(#{sprite_names.join(" ")}, $dimensions, $prefix);
|
83
94
|
}
|
84
95
|
SCSS
|
85
96
|
end
|
86
97
|
|
87
|
-
def
|
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
|
-
|
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 +=
|
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
|
+
|