Compare commits

...

43 Commits

Author SHA1 Message Date
Brandon Mathis
9731c17de1 updated sass brush 2010-12-03 15:56:03 -06:00
Chris Eppstein
0954c8d59b Fix some compilation issues 2010-12-02 21:52:00 -08:00
Chris Eppstein
610f3cdafe Merge branch 'master' into lemonade
* master:
  added new sass brush to doc-src, turned it on

Conflicts:
	doc-src/Gemfile.lock
2010-12-02 21:41:49 -08:00
Chris Eppstein
7aba64eb41 Reference documentation on spriting 2010-12-01 01:35:05 -08:00
Chris Eppstein
f209524228 Only unquote strings 2010-12-01 01:34:07 -08:00
Chris Eppstein
247d6dc8e6 Update gemfiles to depend on sass3.1 2010-12-01 01:33:53 -08:00
Chris Eppstein
1d9e2cbc8a The Sprite container is now called a Sprite Map, and each individual image is called a sprite. 2010-11-30 23:09:31 -08:00
Chris Eppstein
7d763f76d7 Some code comments, cleanup, and register sprite functions with sass. 2010-11-30 22:09:30 -08:00
Chris Eppstein
4603ac9900 Less joins. More responding. 2010-11-30 20:23:01 -08:00
Chris Eppstein
5567e7b13f Change the sprite utilities to create a sprite literal object that can retain information about the sprite for use in other function calls. This avoids the need for global storage and allows multiple sprites constructed from the same sprite folder with different values. 2010-11-30 16:59:30 -08:00
Chris Eppstein
0354dd92a5 Fix broken test. 2010-11-29 22:04:26 -08:00
Chris Eppstein
94fbfd9aa8 Generator for creating sprite imports for users who don't like magic. 2010-11-29 20:53:26 -08:00
Chris Eppstein
40d32606bb Don't rely on reading the environment within the sprite code. Instead accept variable keyword args. 2010-11-29 20:50:50 -08:00
Chris Eppstein
043d082033 Rename these sprite files -- the filenames must be legal css identifiers 2010-11-29 20:27:07 -08:00
Chris Eppstein
d5fa05bc7d Fix a bug in ruby 1.9 2010-11-29 14:36:12 -08:00
Chris Eppstein
8c7223575a Put the Sprite importer on the default load paths coming from compass. 2010-11-29 14:35:37 -08:00
Chris Eppstein
c55896b493 Quick cache for burst reads. 2010-11-29 14:33:57 -08:00
Chris Eppstein
e25508f336 Update links to compass online. 2010-11-27 21:27:46 -08:00
Nico Hagenburger
109687c7d0 added error messages to help lemonade users 2010-11-27 17:00:36 -08:00
Nico Hagenburger
47cec8151c lemonade compatibility specs and functions 2010-11-27 17:00:36 -08:00
Nico Hagenburger
330b39002a allow images to be repeated 2010-11-27 17:00:36 -08:00
Nico Hagenburger
014c609118 updated gem versions 2010-11-27 17:00:36 -08:00
Nico Hagenburger
df05620508 added positioning of images 2010-11-27 17:00:36 -08:00
Nico Hagenburger
c0c39c53d7 generation of png file 2010-11-27 17:00:36 -08:00
Nico Hagenburger
7077b76225 added default spacing 2010-11-27 17:00:36 -08:00
Nico Hagenburger
3e7cd28635 added sprite position calculating 2010-11-27 17:00:36 -08:00
Nico Hagenburger
53d975ffae optimized options 2010-11-27 17:00:36 -08:00
Nico Hagenburger
acd720b41f added spec for base class 2010-11-27 17:00:35 -08:00
Nico Hagenburger
6d0315ad77 added spec for sprite mixin 2010-11-27 17:00:35 -08:00
Nico Hagenburger
c17fe444b6 implemented most basic sprite usage without image generation 2010-11-27 17:00:35 -08:00
Nico Hagenburger
c1756302ca updated to rspec 2.0.0 2010-11-27 17:00:35 -08:00
Chris Eppstein
fdef9d4e44 factor out the saving from the generating, we'll need to monkey patch this in a rails environment. 2010-11-27 17:00:35 -08:00
Chris Eppstein
9380326186 The image_url function has to be accessed from within the engine for it to work right in all cases. 2010-11-27 17:00:35 -08:00
Chris Eppstein
29d39e808d Use the Sass cache to store information about sprites across compiles -- this removes one aspect of lemonade's filesystem dependency. 2010-11-27 17:00:35 -08:00
Chris Eppstein
4b75ef471e This is a little more readable. 2010-11-27 17:00:35 -08:00
Chris Eppstein
62157f6a7e Move the lemonade files around to adhere to compass conventions. 2010-11-27 17:00:35 -08:00
Chris Eppstein
6a44d58b7c Rename Lemonade to Compass::Sprites 2010-11-27 17:00:35 -08:00
Chris Eppstein
ac85ca1e7a This is a whole lot faster 2010-11-27 17:00:34 -08:00
Chris Eppstein
d79dea68db Only do this once, otherwise the output changes every time. 2010-11-27 17:00:34 -08:00
Chris Eppstein
87b624a56e Add a --profile option to the compass CLI 2010-11-27 17:00:34 -08:00
Chris Eppstein
d0f33a74b9 update the gemspec with lemonade dependencies. add nico as an author. 2010-11-27 17:00:34 -08:00
Chris Eppstein
a14a4f33ad Bolt on lemonade to get started. 2010-11-27 17:00:34 -08:00
Chris Eppstein
d1708dd2c3 Use the new Sass gem now. 2010-11-27 17:00:34 -08:00
34 changed files with 1357 additions and 116 deletions

2
.rspec Normal file
View File

@ -0,0 +1,2 @@
--colour
--format s

10
Gemfile
View File

@ -1,12 +1,14 @@
source :rubygems
gem "compass", :path => "."
gem "cucumber"
gem "rspec"
gem "cucumber", "~> 0.9.2"
gem "rspec", "~>2.0.0"
gem "rails", "~>3.0.0.rc"
gem "compass-validator", "3.0.0"
gem "css_parser"
gem "sass", "~> 3.1"
gem "css_parser", "~> 1.0.1"
gem "sass", "~> 3.1", :path => "../sass"
gem "rcov"
gem "rubyzip"
gem "livereload"
gem "chunky_png", "~> 0.10.1"
gem "ruby-prof"

View File

@ -29,6 +29,20 @@ To run with an alternate version of Rails, make test/rails a symlink to that ver
To run with an alternate version of Haml & Sass, make test/haml a symlink to that version.
END
begin
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
RSpec::Core::RakeTask.new(:rcov) do |spec|
spec.rcov = true
end
task :default => :spec
rescue LoadError
puts "Rspec (or a dependency) is not available. Try running bundler install"
end
desc "Compile Examples into HTML and CSS"
task :examples do
linked_haml = "tests/haml"

View File

@ -21,6 +21,21 @@ fallback_load_path(File.join(File.dirname(__FILE__), '..', 'lib')) do
require 'compass/exec'
end
runner = Proc.new do
command_line_class = Compass::Exec::Helpers.select_appropriate_command_line_ui(ARGV)
exit command_line_class.new(ARGV).run!
command_line_class.new(ARGV).run!
end
if ARGV.delete("--profile")
require 'ruby-prof'
RubyProf.start
exit_code = runner.call
result = RubyProf.stop
# Print a flat profile to text
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDERR, 0)
exit exit_code
else
exit runner.call
end

View File

@ -7,7 +7,7 @@ Gem::Specification.new do |gemspec|
gemspec.date = "#{Time.now.year}-#{Time.now.month}-#{Time.now.day}" # Automatically update for each build
gemspec.description = "Compass is a Sass-based Stylesheet Framework that streamlines the creation and maintainance of CSS."
gemspec.homepage = "http://compass-style.org"
gemspec.authors = ["Chris Eppstein", "Eric A. Meyer", "Brandon Mathis"]
gemspec.authors = ["Chris Eppstein", "Eric A. Meyer", "Brandon Mathis", "Nico Hagenburger"]
gemspec.email = "chris@eppsteins.net"
gemspec.default_executable = "compass"
gemspec.executables = %w(compass)
@ -15,7 +15,10 @@ Gem::Specification.new do |gemspec|
gemspec.require_paths = %w(lib)
gemspec.rubygems_version = "1.3.5"
gemspec.summary = %q{A Real Stylesheet Framework}
gemspec.add_dependency('sass', '~> 3.1')
gemspec.add_dependency 'sass', '~> 3.1'
gemspec.add_dependency 'chunky_png', '~> 0.10.3'
gemspec.files = %w(README.markdown LICENSE.markdown VERSION.yml Rakefile)
gemspec.files += Dir.glob("bin/*")
gemspec.files += Dir.glob("examples/**/*.*")

View File

@ -9,7 +9,8 @@ gem 'fssm'
gem 'serve', "1.0.0"
gem 'nokogiri'
gem 'coderay'
gem 'haml', ">=3.0.23", :require => 'sass'
gem 'sass', :path => "../../sass"
gem 'haml', ">= 3.1.0.alpha.36"
gem 'rake'
gem 'compass', :path => ".."
gem 'compass-susy-plugin', ">=0.7.0.pre8"

View File

@ -1,20 +1,27 @@
GIT
remote: git://github.com/chriseppstein/nanoc.git
revision: 4ecb400489c83fd2068659de0c651733b8dad28f
revision: 4eee0e60c5121b90498caa88605d416521553378
specs:
nanoc3 (3.2.0a3)
cri (>= 1.0.0)
PATH
remote: /Users/bmathis/Documents/Workspace/compass-projects/compass
remote: /Users/chris/Projects/compass
specs:
compass (0.11.alpha.1.488fddf)
compass (0.11.alpha.1.610f3cd)
chunky_png (~> 0.10.3)
sass (~> 3.1)
PATH
remote: /Users/chris/Projects/sass
specs:
sass (3.1.0.alpha.0)
GEM
remote: http://rubygems.org/
specs:
activesupport (3.0.1)
chunky_png (0.10.5)
coderay (0.9.5)
compass-susy-plugin (0.8.1)
compass (>= 0.10.0)
@ -22,7 +29,7 @@ GEM
css-slideshow (0.2.0)
compass (>= 0.10.0.rc3)
fssm (0.2.0)
haml (3.0.23)
haml (3.1.0.alpha.36)
i18n (0.4.2)
json (1.4.6)
mime-types (1.16)
@ -30,7 +37,6 @@ GEM
rack (1.2.1)
rake (0.8.7)
rdiscount (1.6.5)
sass (3.1.0.alpha.28)
serve (1.0.0)
activesupport (~> 3.0.1)
i18n (~> 0.4.1)
@ -48,7 +54,7 @@ DEPENDENCIES
compass-susy-plugin (>= 0.7.0.pre8)
css-slideshow (= 0.2.0)
fssm
haml (>= 3.0.23)
haml (>= 3.1.0.alpha.36)
json
mime-types
nanoc3!
@ -56,5 +62,6 @@ DEPENDENCIES
rack
rake
rdiscount
sass!
serve (= 1.0.0)
thor

View File

@ -110,3 +110,4 @@
// CommonJS
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
---
title: Spriting with Compass
layout: tutorial
crumb: Spriting
classnames:
- tutorial
---
# Spriting with Compass

View File

@ -0,0 +1,132 @@
---
title: Compass Sprite Helpers
crumb: Sprites
framework: compass
meta_description: Helper functions for working with Sprite images.
layout: core
classnames:
- reference
- core
- helpers
---
%h1 Compass Sprite Helpers
:markdown
These helpers make it easier to build and to work with sprites.
While it is allowed to use these directly, to do so is considered "advanced usage".
It is recommended that you instead use the sprite mixins that are designed to work
with these functions.
See the [Spriting Tutorial](/help/tutorials/spriting/) for more information.
#sprite-map.helper
%h3
%a(href="#sprite-map")
sprite-map(<span class="arg">$glob</span>, <span class="arg">...</span>)
.details
:markdown
Generates a sprite map from the files matching the glob pattern. Uses the
keyword-style arguments passed in to control the placement.
Only PNG files can be made into sprites at this time.
The `$glob` should be glob pattern relative to the images directory that specifies
what files will be in the sprite. For example:
$icons: sprite-map("icons/*.png");
background: $icons;
This will generate a sprite map and return a reference to it. It's important to
capture this to a variable, because you will need to use it later when creating
sprites. In the above example you might end up with a new file named
`images/sprites/icons-a2ef041.png` and your css stylesheet will have:
background: url('/images/sprites/icons-a2ef041.png?1234678') no-repeat;
The exact image name is not something you should depend on as it may change based on the
arguments you pass in. Instead, you can use the `sprite-url()` function to create a
reference to the sprite map without generating the image again. Alternatively, simply
using the sprite map variable in an property will have the same effect as calling
`sprite-url()`.
For each sprite in the sprite map you can control the position, spacing, and whether or
not it repeats. You do this by passing arguments to this function that tell each sprite
how to behave. For instance if there is a icons/new.png then you can control it like so:
$icon-sprite: sprite-map("icons/*.png",
$new-position: 100%, $new-spacing: 15px, $new-repeat: no-repeat);
If you don't specify these options they will default to `0%` for `position`,
`0px` for spacing, and `no-repeat` for `repeat`.
Default values for all sprites can be specified by passing values for `$position`,
`$spacing`, and `$repeat`.
#sprite.helper
%h3
%a(href="#sprite")
sprite(<span class="arg">$map</span>, <span class="arg">$sprite</span>, <span class="arg" data-default-value="0">$offset-x</span>, <span class="arg" data-default-value="0">$offset-y</span>)
.details
:markdown
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;
#sprite-map-name.helper
%h3
%a(href="#sprite-map-name")
sprite-map-name(<span class="arg">$map</span>)
.details
:markdown
Returns the name of a sprite map
The name is derived from the folder than contains the sprites.
#sprite-file.helper
%h3
%a(href="#sprite-file")
sprite-file(<span class="arg">$map</span>, <span class="arg">$sprite</span>)
.details
:markdown
Returns the relative path (from the images directory) to the original file
used when construction the sprite. This is suitable for passing to the
`image-width` and `image-height` helpers.
#sprite-url.helper
%h3
%a(href="#sprite-url")
sprite-url(<span class="arg">$map</span>)
.details
:markdown
Returns a url to the sprite image.
#sprite-position.helper
%h3
%a(href="#sprite-position")
sprite-position(<span class="arg">$map</span>, <span class="arg">$sprite</span>, <span class="arg" data-default-value="0">$offset-x</span>, <span class="arg" data-default-value="0">$offset-y</span>)
.details
:markdown
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;

View File

@ -0,0 +1,18 @@
---
title: Compass Sprite Base
crumb: Sprite Base
framework: compass
stylesheet: compass/utilities/sprites/_base.scss
layout: core
nav_stylesheet: compass/_utilities.scss
classnames:
- reference
- core
- utilities
---
- render 'reference' do
:markdown
These mixins are useful for working with sprites. This file is imported by
magic sprite imports.
See the [Spriting Tutorial](/help/tutorials/spriting/) for more information.

View File

@ -1,6 +1,6 @@
// Default Syntax Highlighter theme.
//@import "shCore.scss";
//@import "shThemeRDark.scss";
@import "shCore.scss";
@import "shThemeRDark.scss";
/*.syntaxhighlighter {
.keyword { font-weight: bold !important; }

View File

@ -156,6 +156,7 @@ Feature: Command Line
| imports |
| install |
| interactive |
| sprite |
| stats |
| unpack |
| validate |

View File

@ -0,0 +1,38 @@
@mixin image-dimensions($file) {
height: image-height($file);
width: image-width($file);
}
@mixin sprite-image($file) {
background: sprite-image($file) $repeat;
}
@mixin sized-sprite-image($file) {
background: sprite-image($file);
@include image-dimensions($file);
}
@mixin sprite-folder($folder, $image-dimensions: false) {
.#{$folder} {
@if $image-dimensions {
background: sprite-url($folder);
}
@else {
background: sprite-url($folder) no-repeat;
}
}
@for $i from 0 to sprite-files-in-folder($folder) {
$file: sprite-file-from-folder($folder, $i);
.#{$folder}-#{image-basename($file)} {
@extend .#{$folder};
background-position: sprite-position(sprite-file-from-folder($folder, $i));
@if $image-dimensions {
@include image-dimensions($file);
}
}
}
}
@mixin sized-sprite-folder($folder) {
@include sprite-folder($folder, true);
}

View File

@ -9,6 +9,8 @@ $default-background-size: 100% auto !default;
// * percentages are relative to the background-origin (default = padding-box)
// * mixin defaults to: `$default-background-size`
@mixin background-size($size: $default-background-size) {
@if type-of($size) == string {
$size: unquote($size);
}
@include experimental(background-size, $size, -moz, -webkit, -o, not -ms, not -khtml);
}

View File

@ -0,0 +1,40 @@
// Set the width and height of an element to the original
// dimensions of an image before it was included in the sprite.
@mixin sprite-dimensions($map, $sprite) {
height: image-height(sprite-file($map, $sprite));
width: image-width(sprite-file($map, $sprite));
}
// Set the background position of the given sprite `$map` to display the
// sprite of the given `$sprite` name. You can move the image relative to its
// natural position by passing `$offset-x` and `$offset-y`.
@mixin sprite-position($map, $sprite, $offset-x: 0, $offset-y: 0) {
background-position: sprite-position($map, $sprite, $offset-x, $offset-y);
}
// Include the position and (optionally) dimensions of this `$sprite`
// in the given sprite `$map`. The sprite url should come from either a base
// class or you can specify the `sprite-url` explicitly like this:
//
// background: $map no-repeat;
@mixin sprite($map, $sprite, $dimensions: false, $offset-x: 0, $offset-y: 0) {
@include sprite-position($map, $sprite, $offset-x, $offset-y);
@if $dimensions {
@include sprite-dimensions($map, $sprite);
}
}
// Generates a class for each space separated name in `$sprite-names`.
// The class will be of the form .<map-name>-<sprite-name>.
//
// If a base class is provided, then each class will extend it.
//
// If `$dimensions` is `true`, the sprite dimensions will specified.
@mixin sprites($map, $sprite-names, $base-class: false, $dimensions: false) {
@each $sprite-name in $sprite-names {
.#{sprite-map-name($map)}-#{$sprite-name} {
@if $base-class { @extend #{$base-class}; }
@include sprite($map, $sprite-name, $dimensions);
}
}
}

View File

@ -1,7 +1,7 @@
module Compass
end
%w(dependencies util sass_extensions core_ext version errors).each do |lib|
%w(dependencies util sass_extensions core_ext version errors quick_cache).each do |lib|
require "compass/#{lib}"
end
@ -13,8 +13,9 @@ module Compass
File.expand_path(File.join(File.dirname(__FILE__)))
end
module_function :base_directory, :lib_directory
extend QuickCache
end
%w(configuration frameworks app_integration actions compiler).each do |lib|
%w(configuration frameworks app_integration actions compiler sprites).each do |lib|
require "compass/#{lib}"
end

View File

@ -61,7 +61,7 @@ This can be done in one of the following ways:
compass watch [path/to/project]
More Resources:
* Wiki: http://wiki.github.com/chriseppstein/compass
* Website: http://compass-style.org/
* Sass: http://sass-lang.com
* Community: http://groups.google.com/group/compass-users/

View File

@ -5,7 +5,7 @@ require 'compass/commands/registry'
%w(base generate_grid_background help list_frameworks project_base
update_project watch_project create_project imports installer_command
print_version project_stats stamp_pattern validate_project
print_version project_stats stamp_pattern sprite validate_project
write_configuration interactive unpack_extension).each do |lib|
require "compass/commands/#{lib}"
end

View File

@ -0,0 +1,87 @@
require 'compass/commands/project_base'
require 'compass/commands/update_project'
module Compass
module Commands
module SpriteOptionsParser
def set_options(opts)
opts.on("-f SPRITE_FILE") do |output_file|
self.options[:output_file] = output_file
end
opts.banner = %Q{
Usage: compass sprite [options] "images/path/to/sprites/*.png"
Description:
Generate a sprite import based on the given sprite directory.
Alternatively, you can simply do this in your sass files:
@import "sprite-folder/*.png"
And a magical, custom made sprite file will be imported.
Options:
}.strip.split("\n").map{|l| l.gsub(/^ {0,10}/,'')}.join("\n")
super
end
end
class Sprite < ProjectBase
register :sprite
def initialize(working_path, options)
super
assert_project_directory_exists!
end
def perform
sprites = Compass::Sprites.new
relative_uri = options[:uri].gsub(/^#{Compass.configuration.images_dir}\//, '')
sprite_images = Compass::Sprites.discover_sprites(relative_uri)
image_names = sprite_images.map{|i| File.basename(i, '.png')}
sprites.path, sprites.name = Compass::Sprites.path_and_name(relative_uri)
options[:output_file] ||= File.join(Compass.configuration.sass_path, "sprites", "_#{sprites.name}.#{Compass.configuration.preferred_syntax}")
contents = sprites.content_for_images(relative_uri, sprites.name, image_names)
if options[:output_file][-4..-1] != "scss"
contents = Sass::Engine.new(contents, Compass.sass_engine_options.merge(:syntax => :scss)).to_tree.to_sass
end
directory File.dirname(options[:output_file])
write_file options[:output_file], contents
end
class << self
def option_parser(arguments)
parser = Compass::Exec::CommandOptionParser.new(arguments)
parser.extend(Compass::Exec::GlobalOptionsParser)
parser.extend(Compass::Exec::ProjectOptionsParser)
parser.extend(SpriteOptionsParser)
end
def usage
option_parser([]).to_s
end
def description(command)
"Generate an import for your sprites."
end
def parse!(arguments)
parser = option_parser(arguments)
parser.parse!
parse_arguments!(parser, arguments)
parser.options
end
def parse_arguments!(parser, arguments)
parser.options[:uri] = arguments.shift
unless arguments.size == 0
raise Compass::Error, "Please specify at least one image to sprite."
end
end
end
end
end
end

View File

@ -22,6 +22,8 @@ module Compass
plugin_opts[:cache] = cache unless cache.nil?
plugin_opts[:cache_location] = cache_path unless cache_path.nil?
plugin_opts.merge!(sass_options || {})
plugin_opts[:load_paths] ||= []
plugin_opts[:load_paths] << Compass::Sprites.new
plugin_opts
end
@ -57,6 +59,7 @@ module Compass
load_paths << framework.stylesheets_directory if File.exists?(framework.stylesheets_directory)
end
load_paths += resolve_additional_import_paths
load_paths << Compass::Sprites.new
load_paths
end
end

View File

@ -2,5 +2,11 @@ begin
require 'sass'
rescue LoadError
require 'rubygems'
begin
require 'sass'
rescue LoadError
puts "Unable to load Sass. Please install it with one of the following commands:"
puts " gem install sass --pre"
raise
end
end

View File

@ -47,7 +47,7 @@ This can be done in one of the following ways:
compass watch [path/to/project]
More Resources:
* Wiki: http://wiki.github.com/chriseppstein/compass
* Website: http://compass-style.org/
* Sass: http://sass-lang.com
* Community: http://groups.google.com/group/compass-users/
NEXTSTEPS

View File

@ -0,0 +1,15 @@
module QuickCache
# cache a value in memory for just a few seconds
# This can speed up reads of values that change relatively infrequently
# but might be read many times in a short burst of reads.
def quick_cache(key, ttl = 1)
@quick_cache ||= {}
if @quick_cache[key] && @quick_cache[key].first > Time.now - ttl
@quick_cache[key].last
else
(@quick_cache[key] = [Time.now, yield]).last
end
end
end

View File

@ -4,7 +4,7 @@ end
%w(
selectors enumerate urls display
inline_image image_size constants gradient_support
font_files lists colors trig
font_files lists colors trig sprites
).each do |func|
require "compass/sass_extensions/functions/#{func}"
end
@ -22,6 +22,7 @@ module Sass::Script::Functions
include Compass::SassExtensions::Functions::Lists
include Compass::SassExtensions::Functions::Colors
include Compass::SassExtensions::Functions::Trig
include Compass::SassExtensions::Functions::Sprites
end
# Wierd that this has to be re-included to pick up sub-modules. Ruby bug?

View File

@ -13,17 +13,6 @@ module Compass::SassExtensions::Functions::ImageSize
Sass::Script::Number.new(height, ["px"])
end
private
def real_path(image_file)
path = image_file.value
# Compute the real path to the image on the file stystem if the images_dir is set.
if Compass.configuration.images_path
File.join(Compass.configuration.images_path, path)
else
File.join(Compass.configuration.project_path, path)
end
end
class ImageProperties
def initialize(file)
@file = file
@ -55,6 +44,17 @@ private
end
end
private
def real_path(image_file)
path = image_file.value
# Compute the real path to the image on the file stystem if the images_dir is set.
if Compass.configuration.images_path
File.join(Compass.configuration.images_path, path)
else
File.join(Compass.configuration.project_path, path)
end
end
class JPEG
attr_reader :width, :height, :bits

View File

@ -0,0 +1,324 @@
require 'chunky_png'
module Compass::SassExtensions::Functions::Sprites
ZERO = Sass::Script::Number::new(0)
# 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
class SpriteMap < Sass::Script::Literal
attr_accessor :image_names, :path, :name, :options
attr_accessor :images, :width, :height
def self.from_uri(uri, context, kwargs)
path, name = Compass::Sprites.path_and_name(uri.value)
new(Compass::Sprites.discover_sprites(uri.value), path, name, context, kwargs)
end
def initialize(image_names, path, name, context, options)
@image_names, @path, @name, @options = image_names, path, name, options
@images = nil
@width = nil
@height = nil
@evaluation_context = context
validate!
compute_image_metadata!
end
def sprite_names
image_names.map{|f| Compass::Sprites.sprite_name(f) }
end
def validate!
for sprite_name in sprite_names
unless sprite_name =~ /\A#{Sass::SCSS::RX::IDENT}\Z/
raise Sass::SyntaxError, "#{sprite_name} must be a legal css identifier"
end
end
end
# Calculates the overal image dimensions
# collects image sizes and input parameters for each sprite
def compute_image_metadata!
@images = []
@width = 0
image_names.each do |file|
relative_file = file.gsub(Compass.configuration.images_path+"/", "")
width, height = Compass::SassExtensions::Functions::ImageSize::ImageProperties.new(file).size
sprite_name = Compass::Sprites.sprite_name(relative_file)
@width = [@width, width].max
@images << {
:name => sprite_name,
:file => file,
:relative_file => relative_file,
:height => height,
:width => width,
:repeat => repeat_for(sprite_name),
:spacing => spacing_for(sprite_name),
:position => position_for(sprite_name)
}
end
@images.each_with_index do |image, index|
if index == 0
image[:top] = 0
else
last_image = @images[index-1]
image[:top] = last_image[:top] + last_image[:height] + [image[:spacing], last_image[:spacing]].max
end
if image[:position].unit_str == "%"
image[:left] = (@width - image[:width]) * (image[:position].value / 100)
else
image[:left] = image[:position].value
end
end
@height = @images.last[:top] + @images.last[:height]
end
def position_for(name)
options.get_var("#{name}-position") || options.get_var("position") || Sass::Script::Number.new(0, ["px"])
end
def repeat_for(name)
if (var = options.get_var("#{name}-repeat"))
var.value
elsif (var = options.get_var("repeat"))
var.value
else
"no-repeat"
end
end
def spacing_for(name)
(options.get_var("#{name}-spacing") ||
options.get_var("spacing") ||
ZERO).value
end
def image_for(name)
@images.detect{|img| img[:name] == name}
end
# Calculate the size of the sprite
def size
[width, height]
end
# Generate a sprite image if necessary
def generate
if generation_required?
save!(construct_sprite)
end
end
def generation_required?
!File.exists?(filename) || outdated?
end
# Returns a PNG object
def construct_sprite
output_png = ChunkyPNG::Image.new(width, height, ChunkyPNG::Color::TRANSPARENT)
images.each do |image|
input_png = ChunkyPNG::Image.from_file(image[:file])
if image[:repeat] == "no-repeat"
output_png.replace input_png, image[:left], image[:top]
else
x = image[:left] - (image[:left] / image[:width]).ceil * image[:width]
while x < width do
output_png.replace input_png, x, image[:top]
x += image[:width]
end
end
end
output_png
end
# The on-the-disk filename of the sprite
def filename
File.join(Compass.configuration.images_path, "#{path}.png")
end
# saves the sprite for later retrieval
def save!(output_png)
output_png.save filename
end
# All the full-path filenames involved in this sprite
def image_filenames
image_names.map do |image_name|
File.join(Compass.configuration.images_path, image_name)
end
end
# Checks whether this sprite is outdated
def outdated?
last_update = self.mtime
image_filenames.each do |image|
return true if File.mtime(image) > last_update
end
false
end
def mtime
File.mtime(filename)
end
def inspect
to_s
end
def to_s(options = self.options)
sprite_url(self).value
end
def respond_to?(meth)
super || @evaluation_context.respond_to?(meth)
end
def method_missing(meth, *args, &block)
if @evaluation_context.respond_to?(meth)
@evaluation_context.send(meth, *args, &block)
else
super
end
end
end
# Creates a 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
SpriteMap.from_uri(glob, self, kwargs)
end
Sass::Script::Functions.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;
def sprite(map, sprite, offset_x = ZERO, offset_y = ZERO)
unless map.is_a?(SpriteMap)
missing_sprite!("sprite")
end
unless sprite.is_a?(Sass::Script::String)
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
url = sprite_url(map)
position = sprite_position(map, sprite, offset_x, offset_y)
Sass::Script::List.new([url] + position.value, :space)
end
Sass::Script::Functions.declare :sprite, [:map, :sprite]
Sass::Script::Functions.declare :sprite, [:map, :sprite, :offset_x]
Sass::Script::Functions.declare :sprite, [:map, :sprite, :offset_x, :offset_y]
# Returns the name of a sprite map
# The name is derived from the folder than contains the sprites.
def sprite_map_name(map)
unless map.is_a?(SpriteMap)
missing_sprite!("sprite-map-name")
end
Sass::Script::String.new(map.name)
end
Sass::Script::Functions.declare :sprite_name, [:sprite]
# Returns the path to the original image file for the sprite with the given name
def sprite_file(map, sprite)
unless map.is_a?(SpriteMap)
missing_sprite!("sprite-file")
end
if image = map.image_for(sprite.value)
Sass::Script::String.new(image[:relative_file])
else
missing_image!(map, sprite)
end
end
Sass::Script::Functions.declare :sprite_file, [:map, :sprite]
# Returns a url to the sprite image.
def sprite_url(map)
unless map.is_a?(SpriteMap)
missing_sprite!("sprite-url")
end
map.generate
image_url(Sass::Script::String.new("#{map.path}.png"))
end
Sass::Script::Functions.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;
def sprite_position(map, sprite = nil, offset_x = ZERO, offset_y = ZERO)
unless map.is_a?(SpriteMap)
missing_sprite!("sprite-position")
end
unless sprite && sprite.is_a?(Sass::Script::String)
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 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 = Sass::Script::Number.new(x, x == 0 ? [] : ["px"])
end
y = offset_y.value - image[:top]
y = Sass::Script::Number.new(y, y == 0 ? [] : ["px"])
Sass::Script::List.new([x, y],:space)
end
Sass::Script::Functions.declare :sprite_position, [:map]
Sass::Script::Functions.declare :sprite_position, [:map, :sprite]
Sass::Script::Functions.declare :sprite_position, [:map, :sprite, :offset_x]
Sass::Script::Functions.declare :sprite_position, [:map, :sprite, :offset_x, :offset_y]
def sprite_image(*args)
raise Sass::SyntaxError, %Q(The sprite-image() function has been replaced by sprite(). See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
end
protected
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

View File

@ -6,7 +6,7 @@ module Sass
visitor.visit(self)
visitor.down(self) if children.any? and visitor.respond_to?(:down)
if is_a?(ImportNode) && visitor.import?(self)
root = Sass::Files.tree_for(import, @options)
root = Sass::Engine.for_file(import, @options).to_tree
imported_children = root.children
end

122
lib/compass/sprites.rb Normal file
View File

@ -0,0 +1,122 @@
module Compass
class Sprites < Sass::Importers::Base
attr_accessor :name
attr_accessor :path
class << self
def path_and_name(uri)
if uri =~ %r{((.+/)?(.+))/(.+?)\.png}
[$1, $3, $4]
end
end
def discover_sprites(uri)
glob = File.join(Compass.configuration.images_path, uri)
Dir.glob(glob).sort
end
def sprite_name(file)
File.basename(file, '.png')
end
end
def find_relative(*args)
nil
end
def find(uri, options)
if uri =~ /\.png$/
self.path, self.name = Compass::Sprites.path_and_name(uri)
options.merge! :filename => name, :syntax => :scss, :importer => self
sprite_files = Compass::Sprites.discover_sprites(uri)
image_names = sprite_files.map {|i| Compass::Sprites.sprite_name(i) }
Sass::Engine.new(content_for_images(uri, name, image_names), options)
end
end
def content_for_images(uri, name, images)
<<-SCSS
@import "compass/utilities/sprites/base";
// General Sprite Defaults
// You can override them before you import this file.
$#{name}-sprite-base-class: ".#{name}-sprite" !default;
$#{name}-sprite-dimensions: false !default;
$#{name}-position: 0% !default;
$#{name}-spacing: 0 !default;
$#{name}-repeat: no-repeat !default;
// These variables control the generated sprite output
// You can override them selectively before you import this file.
#{images.map do |sprite_name|
<<-SCSS
$#{name}-#{sprite_name}-position: $#{name}-position !default;
$#{name}-#{sprite_name}-spacing: $#{name}-spacing !default;
$#{name}-#{sprite_name}-repeat: $#{name}-repeat !default;
SCSS
end.join}
$#{name}-sprites: sprite-map("#{uri}",
#{images.map do |sprite_name|
%Q{ $#{sprite_name}-position: $#{name}-#{sprite_name}-position,
$#{sprite_name}-spacing: $#{name}-#{sprite_name}-spacing,
$#{sprite_name}-repeat: $#{name}-#{sprite_name}-repeat}
end.join(",\n")});
// All sprites should extend this class
// The #{name}-sprite mixin will do so for you.
\#{$#{name}-sprite-base-class} {
background: $#{name}-sprites no-repeat;
}
// Use this to set the dimensions of an element
// based on the size of the original image.
@mixin #{name}-sprite-dimensions($name) {
@include sprite-dimensions($#{name}-sprites, $name)
}
// Move the background position to display the sprite.
@mixin #{name}-sprite-position($name, $offset-x: 0, $offset-y: 0) {
@include sprite-position($#{name}-sprites, $name, $offset-x, $offset-y)
}
// Extends the sprite base class and set the background position for the desired sprite.
// It will also apply the image dimensions if $dimensions is true.
@mixin #{name}-sprite($name, $dimensions: $#{name}-sprite-dimensions, $offset-x: 0, $offset-y: 0) {
@extend \#{$#{name}-sprite-base-class};
@include sprite($#{name}-sprites, $name, $dimensions, $offset-x, $offset-y)
}
@mixin #{name}-sprites($sprite-names, $dimensions: $#{name}-sprite-dimensions) {
@include sprites($#{name}-sprites, $sprite-names, $#{name}-sprite-base-class, $dimensions)
}
// Generates a class for each sprited image.
@mixin all-#{name}-sprites($dimensions: $#{name}-sprite-dimensions) {
@include #{name}-sprites(#{images.join(" ")}, $dimensions);
}
SCSS
end
def key(uri, options)
[self.class.name + ":" + File.dirname(File.expand_path(uri)),
File.basename(uri)]
end
def mtime(uri, options)
Compass.quick_cache("mtime:#{uri}") do
self.path, self.name = Compass::Sprites.path_and_name(uri)
glob = File.join(Compass.configuration.images_path, uri)
Dir.glob(glob).inject(Time.at(0)) do |max_time, file|
(t = File.mtime(file)) > max_time ? t : max_time
end
end
end
def to_s
""
end
end
end

7
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,7 @@
$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require 'rubygems'
require 'compass'
require 'rspec'
require 'rspec/autorun'

390
spec/sprites_spec.rb Normal file
View File

@ -0,0 +1,390 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
require "compass/sprites"
require 'digest/md5'
describe Compass::Sprites do
before :each do
@images_src_path = File.join(File.dirname(__FILE__), 'test_project', 'public', 'images')
@images_tmp_path = File.join(File.dirname(__FILE__), 'test_project', 'public', 'images-tmp')
FileUtils.cp_r @images_src_path, @images_tmp_path
Compass.configuration.images_path = @images_tmp_path
Compass.configure_sass_plugin!
end
after :each do
FileUtils.rm_r @images_tmp_path
end
def image_size(file)
IO.read(File.join(@images_tmp_path, file))[0x10..0x18].unpack('NN')
end
def image_md5(file)
md5 = Digest::MD5.new
md5.update IO.read(File.join(@images_tmp_path, file))
md5.hexdigest
end
def render(scss)
scss = %Q(@import "compass"; #{scss})
options = Compass.sass_engine_options
options[:line_comments] = false
options[:style] = :expanded
options[:syntax] = :scss
css = Sass::Engine.new(scss, options).render
# reformat to fit result of heredoc:
" #{css.gsub('@charset "UTF-8";', '').gsub(/\n/, "\n ").strip}\n"
end
# DEFAULT USAGE:
it "should generate sprite classes" do
css = render <<-SCSS
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -10px;
}
CSS
image_size('squares.png').should == [20, 30]
image_md5('squares.png').should == 'e8cd71d546aae6951ea44cb01af35820'
end
it "should generate sprite classes with dimensions" do
css = render <<-SCSS
$squares-sprite-dimensions: true;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
height: 10px;
width: 10px;
}
.squares-twenty-by-twenty {
background-position: 0 -10px;
height: 20px;
width: 20px;
}
CSS
image_size('squares.png').should == [20, 30]
end
it "should provide sprite mixin" do
css = render <<-SCSS
@import "squares/*.png";
.cubicle {
@include squares-sprite("ten-by-ten");
}
.large-cube {
@include squares-sprite("twenty-by-twenty", true);
}
SCSS
css.should == <<-CSS
.squares-sprite, .cubicle, .large-cube {
background: url('/squares.png') no-repeat;
}
.cubicle {
background-position: 0 0;
}
.large-cube {
background-position: 0 -10px;
height: 20px;
width: 20px;
}
CSS
image_size('squares.png').should == [20, 30]
end
# CUSTOMIZATIONS:
it "should be possible to change the base class" do
css = render <<-SCSS
$squares-sprite-base-class: ".circles";
@import "squares/*.png";
SCSS
css.should == <<-CSS
.circles {
background: url('/squares.png') no-repeat;
}
CSS
image_size('squares.png').should == [20, 30]
end
it "should calculate the spacing between images but not before first image" do
css = render <<-SCSS
$squares-ten-by-ten-spacing: 33px;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -43px;
}
CSS
image_size('squares.png').should == [20, 63]
end
it "should calculate the spacing between images" do
css = render <<-SCSS
$squares-twenty-by-twenty-spacing: 33px;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -43px;
}
CSS
image_size('squares.png').should == [20, 63]
end
it "should calculate the maximum spacing between images" do
css = render <<-SCSS
$squares-ten-by-ten-spacing: 44px;
$squares-twenty-by-twenty-spacing: 33px;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -54px;
}
CSS
image_size('squares.png').should == [20, 74]
end
it "should calculate the maximum spacing between images in reversed order" do
css = render <<-SCSS
$squares-ten-by-ten-spacing: 33px;
$squares-twenty-by-twenty-spacing: 44px;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -54px;
}
CSS
image_size('squares.png').should == [20, 74]
end
it "should calculate the default spacing between images" do
css = render <<-SCSS
$squares-spacing: 22px;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -32px;
}
CSS
image_size('squares.png').should == [20, 52]
end
it "should use position adjustments in functions" do
css = render <<-SCSS
$squares: sprite-map("squares/*.png", $position: 100%);
.squares-sprite {
background: $squares no-repeat;
}
.adjusted-percentage {
background-position: sprite-position($squares, ten-by-ten, 100%);
}
.adjusted-px-1 {
background-position: sprite-position($squares, ten-by-ten, 4px);
}
.adjusted-px-2 {
background-position: sprite-position($squares, twenty-by-twenty, -3px, 2px);
}
SCSS
css.should == <<-CSS
.squares-sprite {
background: url('/squares.png') no-repeat;
}
.adjusted-percentage {
background-position: 100% 0;
}
.adjusted-px-1 {
background-position: -6px 0;
}
.adjusted-px-2 {
background-position: -3px -8px;
}
CSS
image_size('squares.png').should == [20, 30]
image_md5('squares.png').should == 'b61700e6d402d9df5f3820b73479f371'
end
it "should use position adjustments in mixins" do
css = render <<-SCSS
$squares-position: 100%;
@import "squares/*.png";
.adjusted-percentage {
@include squares-sprite("ten-by-ten", $offset-x: 100%);
}
.adjusted-px-1 {
@include squares-sprite("ten-by-ten", $offset-x: 4px);
}
.adjusted-px-2 {
@include squares-sprite("twenty-by-twenty", $offset-x: -3px, $offset-y: 2px);
}
SCSS
css.should == <<-CSS
.squares-sprite, .adjusted-percentage, .adjusted-px-1, .adjusted-px-2 {
background: url('/squares.png') no-repeat;
}
.adjusted-percentage {
background-position: 100% 0;
}
.adjusted-px-1 {
background-position: -6px 0;
}
.adjusted-px-2 {
background-position: -3px -8px;
}
CSS
image_size('squares.png').should == [20, 30]
image_md5('squares.png').should == 'b61700e6d402d9df5f3820b73479f371'
end
it "should repeat the image" do
css = render <<-SCSS
$squares-repeat: repeat;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -10px;
}
CSS
image_size('squares.png').should == [20, 30]
image_md5('squares.png').should == '0187306f3858136feee87d3017e7f307'
end
it "should provide a nice errors for lemonade's old users" do
proc do
render <<-SCSS
.squares {
background: sprite-url("squares/*.png") no-repeat;
}
SCSS
end.should raise_error Sass::SyntaxError,
%q(The first argument to sprite-url() must be a sprite map. See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
proc do
render <<-SCSS
.squares {
background: sprite-image("squares/twenty-by-twenty.png") no-repeat;
}
SCSS
end.should raise_error Sass::SyntaxError,
%q(The sprite-image() function has been replaced by sprite(). See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
proc do
render <<-SCSS
@import "squares/*.png";
.squares {
background: sprite-position("squares/twenty-by-twenty.png") no-repeat;
}
SCSS
end.should raise_error Sass::SyntaxError,
%q(The first argument to sprite-position() must be a sprite map. See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
end
it "should work even if @import is missing" do
actual_css = render <<-SCSS
.squares {
background: sprite(sprite-map("squares/*.png"), twenty-by-twenty) no-repeat;
}
SCSS
actual_css.should == <<-CSS
.squares {
background: url('/squares.png') 0 -10px no-repeat;
}
CSS
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB