sprite-factory 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +207 -0
  3. data/Rakefile +67 -0
  4. data/bin/sf +46 -0
  5. data/lib/sprite_factory.rb +51 -0
  6. data/lib/sprite_factory/layout.rb +89 -0
  7. data/lib/sprite_factory/library/chunky_png.rb +31 -0
  8. data/lib/sprite_factory/library/rmagick.rb +32 -0
  9. data/lib/sprite_factory/runner.rb +204 -0
  10. data/lib/sprite_factory/style.rb +58 -0
  11. data/sprite_factory.gemspec +24 -0
  12. data/test/images/custom/custom.css +4 -0
  13. data/test/images/custom/running.png +0 -0
  14. data/test/images/custom/stopped.png +0 -0
  15. data/test/images/empty/readme.txt +1 -0
  16. data/test/images/formats/alice.gif +0 -0
  17. data/test/images/formats/monkey.gif +0 -0
  18. data/test/images/formats/spies.jpg +0 -0
  19. data/test/images/formats/thief.png +0 -0
  20. data/test/images/irregular/irregular1.png +0 -0
  21. data/test/images/irregular/irregular2.png +0 -0
  22. data/test/images/irregular/irregular3.png +0 -0
  23. data/test/images/irregular/irregular4.png +0 -0
  24. data/test/images/irregular/irregular5.png +0 -0
  25. data/test/images/irregular/readme.txt +2 -0
  26. data/test/images/reference/custom.css +22 -0
  27. data/test/images/reference/custom.png +0 -0
  28. data/test/images/reference/formats.css +22 -0
  29. data/test/images/reference/formats.png +0 -0
  30. data/test/images/reference/index.html +135 -0
  31. data/test/images/reference/irregular.css +24 -0
  32. data/test/images/reference/irregular.fixed.css +24 -0
  33. data/test/images/reference/irregular.fixed.png +0 -0
  34. data/test/images/reference/irregular.horizontal.css +24 -0
  35. data/test/images/reference/irregular.horizontal.png +0 -0
  36. data/test/images/reference/irregular.padded.css +24 -0
  37. data/test/images/reference/irregular.padded.png +0 -0
  38. data/test/images/reference/irregular.png +0 -0
  39. data/test/images/reference/irregular.sassy.css +38 -0
  40. data/test/images/reference/irregular.sassy.png +0 -0
  41. data/test/images/reference/irregular.sassy.sass +40 -0
  42. data/test/images/reference/irregular.vertical.css +24 -0
  43. data/test/images/reference/irregular.vertical.png +0 -0
  44. data/test/images/reference/regular.css +24 -0
  45. data/test/images/reference/regular.custom.css +24 -0
  46. data/test/images/reference/regular.custom.png +0 -0
  47. data/test/images/reference/regular.fixed.css +24 -0
  48. data/test/images/reference/regular.fixed.png +0 -0
  49. data/test/images/reference/regular.horizontal.css +24 -0
  50. data/test/images/reference/regular.horizontal.png +0 -0
  51. data/test/images/reference/regular.padded.css +24 -0
  52. data/test/images/reference/regular.padded.png +0 -0
  53. data/test/images/reference/regular.png +0 -0
  54. data/test/images/reference/regular.sassy.css +38 -0
  55. data/test/images/reference/regular.sassy.png +0 -0
  56. data/test/images/reference/regular.sassy.sass +40 -0
  57. data/test/images/reference/regular.vertical.css +24 -0
  58. data/test/images/reference/regular.vertical.png +0 -0
  59. data/test/images/reference/s.gif +0 -0
  60. data/test/images/regular/regular1.png +0 -0
  61. data/test/images/regular/regular2.png +0 -0
  62. data/test/images/regular/regular3.png +0 -0
  63. data/test/images/regular/regular4.png +0 -0
  64. data/test/images/regular/regular5.png +0 -0
  65. data/test/integration_test.rb +100 -0
  66. data/test/layout_test.rb +228 -0
  67. data/test/library_test.rb +57 -0
  68. data/test/runner_test.rb +156 -0
  69. data/test/style_test.rb +64 -0
  70. data/test/test_case.rb +127 -0
  71. metadata +159 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Jake Gordon and contributors
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
20
+
@@ -0,0 +1,207 @@
1
+ Sprite Factory
2
+ ==============
3
+
4
+ The sprite factory is a ruby library that can be used to generate
5
+ [CSS sprites](http://www.alistapart.com/articles/sprites). It combines
6
+ individual image files from a directory into a single unified sprite image
7
+ and creates an appropriate CSS stylesheet for use in your web application.
8
+
9
+ The library provides:
10
+
11
+ * both a ruby API and a command line script
12
+ * many customizable options
13
+ * support for any stylesheet syntax, including [CSS](http://www.w3.org/Style/CSS/) and [Sass](http://sass-lang.com/).
14
+ * support for any image library, including [RMagick](http://rmagick.rubyforge.org/) and [ChunkyPNG](https://github.com/wvanbergen/chunky_png).
15
+
16
+
17
+ Installation
18
+ ============
19
+
20
+ $ gem install sprite-factory
21
+
22
+ An image library is also required. SpriteFactory comes with built in support for
23
+ [RMagick](http://rmagick.rubyforge.org/) or
24
+ [ChunkyPng](https://github.com/wvanbergen/chunky_png) and is easily extensible to
25
+ use any image library of your choice.
26
+
27
+ _(see below for instructions to install an image library if you don't already have one.)_
28
+
29
+ Usage
30
+ =====
31
+
32
+ Use the `sf` command line script specifying the location of your images.
33
+
34
+ $ sf images/icons
35
+
36
+ This will combine the individual image files within that directory and generate:
37
+
38
+ * images/icons.png
39
+ * images/icons.css
40
+
41
+ You can also use the SpriteFactory class directly from your own code:
42
+
43
+ require 'sprite_factory'
44
+
45
+ SpriteFactory.run!('images/icons')
46
+
47
+ The original image name is used for the CSS class to show that image in HTML:
48
+
49
+ <img src='s.gif' class='high'>
50
+ <img src='s.gif' class='medium'>
51
+ <img src='s.gif' class='low'>
52
+
53
+ When using a framework such as Rails, you would usually DRY this up with a helper method:
54
+
55
+ def sprite_tag(name)
56
+ image_tag('s.gif', :class => name)
57
+ end
58
+
59
+ Customization
60
+ =============
61
+
62
+ Much of the behavior can be customized by overriding the following options:
63
+
64
+ - `:output` - specify output location for generated files
65
+ - `:layout` - specify layout algorithm (horizontal or vertical)
66
+ - `:style` - specify output style (css or sass)
67
+ - `:library` - specify image library to use (rmagick or chunkypng)
68
+ - `:selector` - specify custom css selector (see below)
69
+ - `:csspath` - specify custom path for css image url (see below)
70
+ - `:padding` - add padding to each sprite
71
+ - `:width` - fix width of each sprite to a specific size
72
+ - `:height` - fix height of each sprite to a specific size
73
+
74
+ Options can be passed as command line arguments to the `sf` script:
75
+
76
+ $ sf images/icons --style sass --layout vertical
77
+
78
+ Options can also be passed as the 2nd argument to the `#run!` method:
79
+
80
+ SpriteFactory.run!('images/icons', :style => :sass, :layout => :vertical)
81
+
82
+ You can see the results of many of these options by viewing the sample page that
83
+ comes with the gem in `test/images/reference/index.html`.
84
+
85
+ Customizing the CSS Selector
86
+ ============================
87
+
88
+ By default, the CSS generated is fairly simple. It assumes you will be using `<img>`
89
+ elements for your sprites, and that the basename of each individual file is suitable for
90
+ use as a CSS classname. For example:
91
+
92
+ img.high { width: 16px; height: 16px; background: url(images/icons.png) 0px 0px no-repeat; }
93
+ img.medium { width: 16px; height: 16px; background: url(images/icons.png) -16px 0px no-repeat; }
94
+ img.low { width: 16px; height: 16px; background: url(images/icons.png) -32px 0px no-repeat; }
95
+
96
+ If you want to use different selectors for your rules, you can provide the `:selector` option. For
97
+ example:
98
+
99
+ SpriteFactory.run!('images/icons', :selector => 'span.icon_')
100
+
101
+ will generate:
102
+
103
+ span.icon_high { width: 16px; height: 16px; background: url(images/icons.png) 0px 0px no-repeat; }
104
+ span.icon_medium { width: 16px; height: 16px; background: url(images/icons.png) -16px 0px no-repeat; }
105
+ span.icon_low { width: 16px; height: 16px; background: url(images/icons.png) -32px 0px no-repeat; }
106
+
107
+ Customizing the CSS Image Path
108
+ ==============================
109
+
110
+ Within the generated CSS file, it can be tricky to get the correct path to your unified
111
+ sprite image. For example, you might be hosting your images on Amazon S3, or if you are
112
+ building a Ruby on Rails application you might need to generate URL's using the `#image_path`
113
+ helper method to ensure it gets the appopriate cache-busting query parameter.
114
+
115
+ By default, the SpriteFactory generates simple url's that contain only the basename of the
116
+ unified sprite image, but you can control the generation of these url's using the :csspath
117
+ option:
118
+
119
+ For most CDN's, you can prepend a simple string to the image name:
120
+
121
+ SpriteFactory.run('images/icons',
122
+ :csspath => "http://s3.amazonaws.com/")
123
+
124
+ # generates: url(http://s3.amazonaws.com/icons.png)
125
+
126
+ For more control, you can provide a lambda function and generate your own paths:
127
+
128
+ SpriteFactory.run('images/icons',
129
+ :csspath => lambda{|image| image_path(image)})
130
+
131
+ # generates: url(/images/icons.png?v123456)
132
+
133
+ Customizing the entire CSS output
134
+ =================================
135
+
136
+ If you want **complete** control over the generated styles, you can pass a block to the `run!` method.
137
+
138
+ The block will be provided with information about each image, including the generated css attributes.
139
+ Whatever content the block returns will be inserted into the generated css file.
140
+
141
+ SpriteFactory.run!('images/timer') do |images|
142
+ rules = []
143
+ rules << "div.running img.button { cursor: pointer; #{images[:running][:style]} }"
144
+ rules << "div.stopped img.button { cursor: pointer; #{images[:stopped][:style]} }"
145
+ rules.join("\n")
146
+ end
147
+
148
+ The `images` argument is a hash, where each key is the basename of an image file, and the
149
+ value is a hash of image metadata that includes the following:
150
+
151
+ * `:style` - the default generated style
152
+ * `:cssx` - the css sprite x position
153
+ * `:cssy` - the css sprite y position
154
+ * `:cssw` - the css sprite width
155
+ * `:cssh` - the css sprite height
156
+ * `:x` - the image x position
157
+ * `:y` - the image y position
158
+ * `:width` - the image width
159
+ * `:height` - the image height
160
+
161
+ (*NOTE*: the image coords can differ form the css sprite coords when padding or fixed width/height options are specified)
162
+
163
+ Extending the Library
164
+ =====================
165
+
166
+ The sprite factory library can also be extended in a number of other ways.
167
+
168
+ * provide a custom layout algorithm in the `SpriteFactory::Layout` module.
169
+ * provide a custom style generator in the `SpriteFactory::Style` module.
170
+ * provide a custom image library in the `SpriteFactory::Library` module.
171
+
172
+ _(see existing code for examples of each)._
173
+
174
+ Installing an Image Library
175
+ ===========================
176
+
177
+ SpriteFactory comes with built in support for
178
+ [RMagick](http://rmagick.rubyforge.org/) or
179
+ [ChunkyPng](https://github.com/wvanbergen/chunky_png)
180
+
181
+ RMagick is the most flexible image libary to use, but requires ImageMagick
182
+ binaries, installation instructions for ubuntu:
183
+
184
+ $ sudo aptitude install imageMagick libMagickWand-dev
185
+ $ sudo gem install rmagick
186
+
187
+ ChunkyPng is lighter weight and has no binary requirements, but only supports
188
+ .png format. Installation is a simple gem install:
189
+
190
+ $ gem install chunky_png
191
+
192
+ SpriteFactory can also be easily extended to use the image library of your choice.
193
+
194
+ License
195
+ =======
196
+
197
+ See LICENSE file.
198
+
199
+ Contact
200
+ =======
201
+
202
+ You can reach me at [jake@codeincomplete.com](mailto:jake@codeincomplete.com), or via
203
+ my website: [Code inComplete](http://codeincomplete.com).
204
+
205
+
206
+
207
+
@@ -0,0 +1,67 @@
1
+ require 'rake/testtask'
2
+
3
+ #------------------------------------------------------------------------------
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ #------------------------------------------------------------------------------
11
+
12
+ desc "run a console with SpriteFactory loaded"
13
+ task :console do
14
+ system "irb -r #{File.expand_path('lib/sprite_factory', File.dirname(__FILE__))}"
15
+ end
16
+
17
+ #------------------------------------------------------------------------------
18
+
19
+ desc "regenerate test reference images"
20
+ task :reference do
21
+
22
+ require File.expand_path('lib/sprite_factory', File.dirname(__FILE__))
23
+
24
+ regenerate = lambda do |input, options = {}, &block|
25
+ output = options[:output] || input
26
+ SpriteFactory.run!(input, {:report => true}.merge(options), &block)
27
+ FileUtils.mv(output + "." + ( :png).to_s, 'test/images/reference')
28
+ FileUtils.mv(output + "." + (options[:style] || :css).to_s, 'test/images/reference')
29
+ end
30
+
31
+ regenerate.call('test/images/regular')
32
+ regenerate.call('test/images/regular', :output => 'test/images/regular.horizontal', :selector => 'img.horizontal_', :layout => :horizontal)
33
+ regenerate.call('test/images/regular', :output => 'test/images/regular.vertical', :selector => 'img.vertical_', :layout => :vertical)
34
+ regenerate.call('test/images/regular', :output => 'test/images/regular.padded', :selector => 'img.padded_', :padding => 10)
35
+ regenerate.call('test/images/regular', :output => 'test/images/regular.fixed', :selector => 'img.fixed_', :width => 100, :height => 100)
36
+ regenerate.call('test/images/regular', :output => 'test/images/regular.sassy', :selector => 'img.sassy_', :style => :sass)
37
+
38
+ regenerate.call('test/images/irregular')
39
+ regenerate.call('test/images/irregular', :output => 'test/images/irregular.horizontal', :selector => 'img.horizontal_', :layout => :horizontal)
40
+ regenerate.call('test/images/irregular', :output => 'test/images/irregular.vertical', :selector => 'img.vertical_', :layout => :vertical)
41
+ regenerate.call('test/images/irregular', :output => 'test/images/irregular.padded', :selector => 'img.padded_', :padding => 10)
42
+ regenerate.call('test/images/irregular', :output => 'test/images/irregular.fixed', :selector => 'img.fixed_', :width => 100, :height => 100)
43
+ regenerate.call('test/images/irregular', :output => 'test/images/irregular.sassy', :selector => 'img.sassy_', :style => :sass)
44
+
45
+ regenerate.call('test/images/custom', :output => 'test/images/custom') do |images|
46
+ rules = []
47
+ rules << "div.running img.button { cursor: pointer; #{images[:running][:style]} }"
48
+ rules << "div.stopped img.button { cursor: pointer; #{images[:stopped][:style]} }"
49
+ rules.join("\n")
50
+ end
51
+
52
+ regenerate.call('test/images/formats', :library => :rmagick)
53
+
54
+ end
55
+
56
+ #------------------------------------------------------------------------------
57
+
58
+ desc "convert reference test sass files to css"
59
+ task :sass do
60
+
61
+ `sass 'test/images/reference/regular.sassy.sass' 'test/images/reference/regular.sassy.css'`
62
+ `sass 'test/images/reference/irregular.sassy.sass' 'test/images/reference/irregular.sassy.css'`
63
+
64
+ end
65
+
66
+ #------------------------------------------------------------------------------
67
+
data/bin/sf ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.push File.expand_path("../lib", File.dirname(__FILE__)) # add sprite factory library to load path
4
+
5
+ require 'sprite_factory'
6
+ require 'optparse'
7
+
8
+ options = { :report => true }
9
+ op = OptionParser.new
10
+ op.banner = "#{SpriteFactory::DESCRIPTION}\nUsage: sprite <dir> [options]"
11
+
12
+ op.on("-h", "--help") do
13
+ puts op.to_s
14
+ exit
15
+ end
16
+
17
+ op.on("-v", "--version") do
18
+ puts SpriteFactory::VERSION
19
+ exit
20
+ end
21
+
22
+ output_help = "specify output location, without any extension"
23
+ layout_help = "specify layout orientation ( horizontal, vertical )"
24
+ style_help = "specify output style format ( css, sass )"
25
+ library_help = "specify image library to use ( rmagic, chunkypng )"
26
+ selector_help = "specify custom selector to use for each css rule ( default: 'img.' )"
27
+ csspath_help = "specify custom path to use for css image urls ( default: output file's basename )"
28
+
29
+ op.on("--output [PATH]", output_help) {|value| options[:output] = value }
30
+ op.on("--layout [ORIENTATION]", layout_help) {|value| options[:layout] = value }
31
+ op.on("--style [STYLE]", style_help) {|value| options[:style] = value }
32
+ op.on("--library [LIBRARY]", library_help) {|value| options[:library] = value }
33
+ op.on("--selector [SELECTOR]", selector_help) {|value| options[:selector] = value }
34
+ op.on("--csspath [CSSPATH]", csspath_help) {|value| options[:csspath] = value }
35
+
36
+ begin
37
+ op.parse!(ARGV)
38
+ raise "a single argument must be specified containing images to be sprited" if ARGV.empty?
39
+ SpriteFactory.run!(ARGV[0], options)
40
+ rescue Exception => ex
41
+ puts ex.message
42
+ puts op.to_s
43
+ exit
44
+ end
45
+
46
+
@@ -0,0 +1,51 @@
1
+ module SpriteFactory
2
+
3
+ #----------------------------------------------------------------------------
4
+
5
+ VERSION = "1.0.0"
6
+ SUMMARY = "Automatic CSS sprite generator"
7
+ DESCRIPTION = "Combines individual images from a directory into a single sprite image file and creates an appropriate CSS stylesheet"
8
+ LIB = File.dirname(__FILE__)
9
+
10
+ autoload :Runner, File.join(LIB, 'sprite_factory/runner') # controller that glues everything together
11
+ autoload :Layout, File.join(LIB, 'sprite_factory/layout') # layout calculations
12
+ autoload :Style, File.join(LIB, 'sprite_factory/style') # style generators
13
+
14
+ def self.run!(input, config = {}, &block)
15
+ Runner.new(input, config).run!(&block)
16
+ end
17
+
18
+ #
19
+ # fallback defaults for some options can be set at module level to
20
+ # avoid having to pass them to #run! every single time
21
+ #
22
+ class << self
23
+ attr_accessor :report
24
+ attr_accessor :style
25
+ attr_accessor :layout
26
+ attr_accessor :library
27
+ attr_accessor :selector
28
+ attr_accessor :csspath
29
+ end
30
+
31
+ #----------------------------------------------------------------------------
32
+
33
+ module Library # abstract module for using various image libraries
34
+
35
+ autoload :RMagick, File.join(LIB, 'sprite_factory/library/rmagick') # concrete module for using RMagick (loaded on demand)
36
+ autoload :ChunkyPng, File.join(LIB, 'sprite_factory/library/chunky_png') # concrete module for using ChunkyPng (ditto)
37
+
38
+ def self.rmagick
39
+ RMagick
40
+ end
41
+
42
+ def self.chunkypng
43
+ ChunkyPng
44
+ end
45
+
46
+ end
47
+
48
+ #----------------------------------------------------------------------------
49
+
50
+ end
51
+
@@ -0,0 +1,89 @@
1
+ module SpriteFactory
2
+ module Layout
3
+
4
+ #--------------------------------------------------------------------------
5
+
6
+ def self.horizontal(images, options = {})
7
+ width = options[:width]
8
+ height = options[:height]
9
+ hpadding = options[:hpadding] || 0
10
+ vpadding = options[:vpadding] || 0
11
+ max_height = height || ((2 * vpadding) + images.map{|i| i[:height]}.max)
12
+ x = 0
13
+ images.each do |i|
14
+
15
+ if width
16
+ i[:cssw] = width
17
+ i[:cssx] = x
18
+ i[:x] = x + (width - i[:width]) / 2
19
+ else
20
+ i[:cssw] = i[:width] + (2 * hpadding) # image width plus padding
21
+ i[:cssx] = x # anchored at x
22
+ i[:x] = i[:cssx] + hpadding # image drawn offset to account for padding
23
+ end
24
+
25
+ if height
26
+ i[:cssh] = height
27
+ i[:cssy] = 0
28
+ i[:y] = 0 + (height - i[:height]) / 2
29
+ else
30
+ i[:cssh] = i[:height] + (2 * vpadding) # image height plus padding
31
+ i[:cssy] = (max_height - i[:cssh]) / 2 # centered vertically
32
+ i[:y] = i[:cssy] + vpadding # image drawn offset to account for padding
33
+ end
34
+
35
+ x = x + i[:cssw]
36
+
37
+ end
38
+ { :width => x, :height => max_height }
39
+ end
40
+
41
+ #--------------------------------------------------------------------------
42
+
43
+ def self.vertical(images, options = {})
44
+ width = options[:width]
45
+ height = options[:height]
46
+ hpadding = options[:hpadding] || 0
47
+ vpadding = options[:vpadding] || 0
48
+ max_width = width || ((2 * hpadding) + images.map{|i| i[:width]}.max)
49
+ y = 0
50
+ images.each do |i|
51
+
52
+ if width
53
+ i[:cssw] = width
54
+ i[:cssx] = 0
55
+ i[:x] = 0 + (width - i[:width]) / 2
56
+ else
57
+ i[:cssw] = i[:width] + (2 * hpadding) # image width plus padding
58
+ i[:cssx] = (max_width - i[:cssw]) / 2 # centered horizontally
59
+ i[:x] = i[:cssx] + hpadding # image drawn offset to account for padding
60
+ end
61
+
62
+ if height
63
+ i[:cssh] = height
64
+ i[:cssy] = y
65
+ i[:y] = y + (height - i[:height]) / 2
66
+ else
67
+ i[:cssh] = i[:height] + (2 * vpadding) # image height plus padding
68
+ i[:cssy] = y # anchored at y
69
+ i[:y] = i[:cssy] + vpadding # image drawn offset to account for padding
70
+ end
71
+
72
+ y = y + i[:cssh]
73
+
74
+ end
75
+ { :width => max_width, :height => y }
76
+ end
77
+
78
+ #--------------------------------------------------------------------------
79
+
80
+ def self.knapsack(images)
81
+
82
+ raise NotImplementedError, "one day, when I have time, I'll do some kind of 'best-fit' algorithm"
83
+
84
+ end
85
+
86
+ #--------------------------------------------------------------------------
87
+
88
+ end
89
+ end