File: sprites.rb

package info (click to toggle)
ruby-compass 1.0.1~dfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 8,152 kB
  • ctags: 1,795
  • sloc: ruby: 12,901; makefile: 127; perl: 43; xml: 14; sh: 4
file content (305 lines) | stat: -rw-r--r-- 10,550 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
module Compass::SassExtensions::Functions::Sprites

  extend Compass::SassExtensions::Functions::SassDeclarationHelper
  extend Sass::Script::Value::Helpers
  include Sass::Script::Value::Helpers

  ZERO = number(0)
  BOOL_FALSE = bool(false)
  VALID_SELECTORS = %w(hover active target focus)

  # 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

  # Returns the width of the generated sprite map
  def sprite_width(map, sprite=nil)
    verify_map(map, 'sprite-width')
    file = get_sprite_file(map, sprite)
    width, _ = image_dimensions(file)
    number(width, "px")
  end
  declare :sprite_width, [:map]
  declare :sprite_width, [:map, :sprite]
  
  # Returns the height of the generated sprite map
  def sprite_height(map, sprite=nil)
    verify_map(map, 'sprite-height')
    file = get_sprite_file(map, sprite)
    _, height = image_dimensions(file)
    number(height, "px")
  end
  declare :sprite_height, [:map]
  declare :sprite_height, [:map, :sprite]

  # Returns a list of all sprite names
  def sprite_names(map)
    verify_map(map, 'sprite-names')
    list(map.sprite_names.map { |f| identifier(f) }, :comma)
  end
  declare :sprite_names, [:map]

  # Returns the system path of the sprite file
  def sprite_path(map)
    verify_map(map, 'sprite-path')
    identifier(map.filename)
  end
  declare :sprite_path, [:map]

  # Returns the sprite file as an inline image
  #    @include "icon/*.png";
  #     #{$icon-sprite-base-class} {
  #       background-image: inline-sprite($icon-sprites);
  #      }
  def inline_sprite(map)
    verify_map(map, "sprite-url")
    map.generate
    path = map.filename
    inline_image_string(data(path), compute_mime_type(path))
  end
  declare :inline_sprite, [:map]

  # Creates a Compass::SassExtensions::Sprites::SpriteMap object. A sprite map, when used in a property is the same
  # as calling sprite-url. So the following background properties are equivalent:
  #
  #     $icons: sprite-map("icons/*.png");
  #     background: sprite-url($icons) no-repeat;
  #     background: $icons no-repeat;
  #
  # The sprite map object will generate the sprite map image, if necessary,
  # the first time it is converted to a url. Simply constructing it has no side-effects.
  def sprite_map(glob, kwargs = {})
    kwargs.extend VariableReader
    Compass::SassExtensions::Sprites::SpriteMap.from_uri(glob, self, kwargs)
  end
  declare :sprite_map, [:glob], :var_kwargs => true

  # Returns the image and background position for use in a single shorthand property:
  #
  #     $icons: sprite-map("icons/*.png"); // contains icons/new.png among others.
  #     background: sprite($icons, new) no-repeat;
  #
  # Becomes:
  #
  #     background: url('/images/icons.png?12345678') 0 -24px no-repeat;
  #
  # If the `use_percentages` parameter is passed as true, percentages will be
  # used to position the sprite. Example output:
  #     
  #     background: url('/images/icons.png?12345678') 0 50% no-repeat;
  #
  def sprite(map, sprite, offset_x = ZERO, offset_y = ZERO, use_percentages = BOOL_FALSE)
    sprite = convert_sprite_name(sprite)
    verify_map(map)
    verify_sprite(sprite)
    url = sprite_url(map)
    position = sprite_position(map, sprite, offset_x, offset_y, use_percentages)
    list([url] + position.value, :space)
  end
  declare :sprite, [:map, :sprite]
  declare :sprite, [:map, :sprite, :offset_x]
  declare :sprite, [:map, :sprite, :offset_x, :offset_y]
  declare :sprite, [:map, :sprite, :offset_x, :offset_y, :use_percentages]

  # Returns the name of a sprite map
  # The name is derived from the folder than contains the sprites.
  def sprite_map_name(map)
    verify_map(map, "sprite-map-name")
    identifier(map.name)
  end
  declare :sprite_name, [:sprite]

  # Returns the path to the original image file for the sprite with the given name
  def sprite_file(map, sprite)
    sprite = convert_sprite_name(sprite)
    verify_map(map, "sprite")
    verify_sprite(sprite)
    if image = map.image_for(sprite.value)
      image_path = Pathname.new(File.expand_path(image.file))
      images_path = Pathname.new(File.expand_path(Compass.configuration.images_path))
      quoted_string(image_path.relative_path_from(images_path).to_s)
    else
      missing_image!(map, sprite)
    end
  end
  declare :sprite_file, [:map, :sprite]

  # Returns boolean if sprite has a parent
  def sprite_does_not_have_parent(map, sprite)
    sprite = convert_sprite_name(sprite)
    verify_map map
    verify_sprite sprite
    bool(map.image_for(sprite.value).parent.nil?)
  end
  declare :sprite_does_not_have_parent, [:map, :sprite]

  #return the name of the selector file
  def sprite_selector_file(map, sprite, selector)
    sprite = convert_sprite_name(sprite)
    image = map.image_for(sprite)
    if map.send(:"has_#{selector.value}?", sprite.value)
      return identifier(image.send(selector.value).name)
    end

    raise Sass::SyntaxError, "Sprite: #{sprite.value} does not have a #{selector} state"
  end

  declare :sprite_selector_file, [:map, :sprite, :selector]

  # Returns boolean if sprite has the selector
  def sprite_has_selector(map, sprite, selector)
    sprite = convert_sprite_name(sprite)
    verify_map map
    verify_sprite sprite
    unless VALID_SELECTORS.include?(selector.value)
      raise Sass::SyntaxError, "Invalid Selctor did you mean one of: #{VALID_SELECTORS.join(', ')}"
    end
    bool map.send(:"has_#{selector.value}?", sprite.value)
  end
  
  declare :sprite_has_selector, [:map, :sprite, :selector]

  # Determines if the CSS selector is valid
  IDENTIFIER_RX = /\A#{Sass::SCSS::RX::IDENT}\Z/
  def sprite_has_valid_selector(selector)
    unless selector.value =~ IDENTIFIER_RX
      raise Sass::SyntaxError, "#{selector} must be a legal css identifier"
    end
    bool true
  end

  # Returns a url to the sprite image.
  def sprite_url(map)
    verify_map(map, "sprite-url")
    map.generate
    generated_image_url(identifier("#{map.path}-s#{map.uniqueness_hash}.png"))
  end
  declare :sprite_url, [:map]

  # Returns the position for the original image in the sprite.
  # This is suitable for use as a value to background-position:
  #
  #     $icons: sprite-map("icons/*.png");
  #     background-position: sprite-position($icons, new);
  #
  # Might generate something like:
  #
  #     background-position: 0 -34px;
  #
  # You can adjust the background relative to this position by passing values for
  # `$offset-x` and `$offset-y`:
  #
  #     $icons: sprite-map("icons/*.png");
  #     background-position: sprite-position($icons, new, 3px, -2px);
  #
  # Would change the above output to:
  #
  #     background-position: 3px -36px;
  #
  # If you set the `use_percentages` parameter to true, the position will be
  # expressed in percentages. An example:
  #
  #     background-position: sprite-position($icons, new, 0, 0, true);
  #
  # Would result in something like this:
  #
  #     background-position: 0 42%;
  # 
  def sprite_position(map, sprite = nil, offset_x = ZERO, offset_y = ZERO, use_percentages = BOOL_FALSE)
    assert_type offset_x, :Number
    assert_type offset_y, :Number
    sprite = convert_sprite_name(sprite)
    verify_map(map, "sprite-position")
    unless sprite.is_a?(Sass::Script::Value::String) || sprite.is_a?(Sass::Script::Value::Number)
      raise Sass::SyntaxError, %Q(The second argument to sprite-position must be a sprite name. See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
    end
    image = map.image_for(sprite.value)
    unless image
      missing_image!(map, sprite)
    end
    if use_percentages.value
      xdivis = map.width - image.width;
      x = (offset_x.value + image.left.to_f) / (xdivis.nonzero? || 1) * 100
      x = x == 0 ? number(x) : number(x, "%")
      ydivis = map.height - image.height;
      y = (offset_y.value + image.top.to_f) / (ydivis.nonzero? || 1) * 100
      y = y == 0 ? number(y) : number(y, "%")
    else
      if offset_x.unit_str == "%"
        x = offset_x # CE: Shouldn't this be a percentage of the total width?
      else
        x = offset_x.value - image.left
        x = x == 0 ? number(x) : number(x, "px")
      end
      y = offset_y.value - image.top
      y = y == 0 ? number(y) : number(y, "px")
    end
    list(x, y, :space)
  end
  declare :sprite_position, [:map]
  declare :sprite_position, [:map, :sprite]
  declare :sprite_position, [:map, :sprite, :offset_x]
  declare :sprite_position, [:map, :sprite, :offset_x, :offset_y]
  declare :sprite_position, [:map, :sprite, :offset_x, :offset_y, :use_percentages]

protected

  def get_sprite_file(map, sprite=nil)
    if sprite
      map.image_for(sprite).file
    else
      map.filename
    end
  end

  def reversed_color_names
    if Sass::Script::Value::Color.const_defined?(:HTML4_COLORS_REVERSE)
      Sass::Script::Value::Color::HTML4_COLORS_REVERSE
    else
      Sass::Script::Value::Color::COLOR_NAMES_REVERSE
    end
  end

  def convert_sprite_name(sprite)
    case sprite
      when Sass::Script::Value::Color
        rgb = if reversed_color_names.keys.first.size == 3
                sprite.rgb
              else
                # Sass 3.3 includes the alpha channel
                sprite.rgba
              end
        identifier(reversed_color_names[rgb])
      when Sass::Script::Value::Bool
        identifier(sprite.to_s)
      else
        sprite
    end
  end

  def verify_map(map, error = "sprite")
    unless map.is_a?(Compass::SassExtensions::Sprites::SpriteMap)
      missing_sprite!(error)
    end
  end

  def verify_sprite(sprite)
    unless sprite.is_a?(Sass::Script::Value::String) || sprite.is_a?(Sass::Script::Value::Number)
      raise Sass::SyntaxError, %Q(The second argument to sprite() must be a sprite name. See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
    end
  end

  def missing_image!(map, sprite)
    raise Sass::SyntaxError, "No sprite called #{sprite} found in sprite map #{map.path}/#{map.name}. Did you mean one of: #{map.sprite_names.join(", ")}"
  end

  def missing_sprite!(function_name)
    raise Sass::SyntaxError, %Q(The first argument to #{function_name}() must be a sprite map. See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
  end

end