Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions scss/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,32 +844,32 @@ def _at_magic_import(self, calculator, rule, scope, block):
Implements @import for sprite-maps
Imports magic sprite map directories
"""
# TODO check that the found file is actually under the root

to_import = block.argument.strip('"')
if callable(config.STATIC_ROOT):
files = sorted(config.STATIC_ROOT(block.argument))
files = sorted(config.STATIC_ROOT(to_import))
else:
glob_path = os.path.join(config.STATIC_ROOT, block.argument)
glob_path = os.path.join(config.STATIC_ROOT, to_import)
files = glob.glob(glob_path)
files = sorted((file[len(config.STATIC_ROOT):], None) for file in files)

if not files:
return

# Build magic context
map_name = os.path.normpath(os.path.dirname(block.argument)).replace('\\', '_').replace('/', '_')
# Populate namespace with sprite variables
map_name = os.path.normpath(os.path.dirname(to_import)).replace(os.sep, '_')
kwargs = {}

def setdefault(var, val):
_var = '$' + map_name + '-' + var
if _var in rule.context:
kwargs[var] = calculator.interpolate(rule.context[_var], rule, self._library)
else:
rule.context[_var] = val
kwargs[var] = calculator.interpolate(val, rule, self._library)
return rule.context[_var]
if _var not in rule.namespace.variables:
rule.namespace.set_variable(_var, val)
kwargs[var] = calculator.interpolate(_var)
return rule.namespace.variable(_var)

setdefault('sprite-base-class', String('.' + map_name + '-sprite', quotes=None))
setdefault('sprite-dimensions', Boolean(False))
setdefault('layout', String('vertical'))
position = setdefault('position', Number(0, '%'))
spacing = setdefault('spacing', Number(0))
repeat = setdefault('repeat', String('no-repeat', quotes=None))
Expand All @@ -878,7 +878,7 @@ def setdefault(var, val):
setdefault(n + '-position', position)
setdefault(n + '-spacing', spacing)
setdefault(n + '-repeat', repeat)
rule.context['$' + map_name + '-' + 'sprites'] = sprite_map(block.argument, **kwargs)
rule.namespace.set_variable('$' + map_name + '-' + 'sprites', sprite_map(block.argument, **kwargs))
ret = '''
@import "compass/utilities/sprites/base";

Expand Down
122 changes: 88 additions & 34 deletions scss/functions/compass/sprites.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def sprite_map(g, **kwargs):
Generates a sprite map from the files matching the glob pattern.
Uses the keyword-style arguments passed in to control the placement.

$direction - Sprite map layout. Can be `vertical` (default), `horizontal`, `diagonal` or `smart`.
$direction, $layout - Sprite map layout. Can be `vertical` (default), `horizontal`, `diagonal` or `smart`.

$position - For `horizontal` and `vertical` directions, the position of the sprite. (defaults to `0`)
$<sprite>-position - Position of a given sprite.
Expand All @@ -123,7 +123,7 @@ def sprite_map(g, **kwargs):
now_time = time.time()

globs = String(g, quotes=None).value
globs = sorted(g.strip() for g in globs.split(','))
globs = sorted(g.strip(' "') for g in globs.split(','))

_k_ = ','.join(globs)

Expand Down Expand Up @@ -151,7 +151,7 @@ def sprite_map(g, **kwargs):
if _files:
files.extend(_files)
rfiles.extend(_rfiles)
base_name = os.path.normpath(os.path.dirname(_glob)).replace('\\', '_').replace('/', '_')
base_name = os.path.normpath(os.path.dirname(_glob)).replace(os.sep, '_')
_map_name, _, _map_type = base_name.partition('.')
if _map_type:
_map_type += '-'
Expand All @@ -166,7 +166,13 @@ def sprite_map(g, **kwargs):
log.error("Nothing found at '%s'", glob_path)
return String.unquoted('')

key = [f for (f, s) in files] + [repr(kwargs), config.ASSETS_URL]
# sorting kwargs representation to ensure hash repeatability
kwargs_repr = ""
for k in sorted(kwargs.keys()):
kwargs_repr += ("'%s': %s, " % (k, repr(kwargs[k])))
kwargs_repr = str("{%s}" % kwargs_repr.strip(', '))

key = [f for (f, s) in files] + [kwargs_repr, config.ASSETS_URL]
key = map_name + '-' + make_filename_hash(key)
asset_file = key + '.png'
ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets')
Expand Down Expand Up @@ -204,7 +210,9 @@ def sprite_map(g, **kwargs):

if sprite_map is None or asset is None:
cache_buster = Boolean(kwargs.get('cache_buster', True))
direction = String.unquoted(kwargs.get('direction', config.SPRTE_MAP_DIRECTION)).value
direction = String.unquoted(kwargs.get('direction',
kwargs.get('layout',
config.SPRTE_MAP_DIRECTION))).value
repeat = String.unquoted(kwargs.get('repeat', 'no-repeat')).value
collapse = kwargs.get('collapse', Number(0))
if isinstance(collapse, List):
Expand Down Expand Up @@ -326,6 +334,7 @@ def images(f=lambda x: x):

offsets_x = []
offsets_y = []
selectors = []
for i, image in enumerate(images()):
x, y, width, height, cssx, cssy, cssw, cssh = layout_positions[i]
iwidth, iheight = image.size
Expand Down Expand Up @@ -359,6 +368,13 @@ def images(f=lambda x: x):
offsets_x.append(cssx)
offsets_y.append(cssy)

# extracting selector for compass spriting's magic selectors
# http://compass-style.org/help/tutorials/spriting/magic-selectors/
name = os.path.splitext(os.path.basename(image.filename))[0]
spl = name.split('_')
selector = spl[-1] if len(spl) > 1 else None
selectors.append(selector)

if useless_dst_color:
log.warning("Useless use of $dst-color in sprite map for files at '%s' (never used for)" % glob_path)

Expand Down Expand Up @@ -388,12 +404,13 @@ def images(f=lambda x: x):
asset = file_asset = List([String.unquoted(url), String.unquoted(repeat)])

# Add the new object:
sprite_map = dict(zip(tnames, zip(sizes, rfiles, offsets_x, offsets_y)))
sprite_map = dict(zip(tnames, zip(sizes, rfiles, offsets_x, offsets_y, selectors)))
sprite_map['*'] = now_time
sprite_map['*f*'] = asset_file
sprite_map['*k*'] = key
sprite_map['*n*'] = map_name
sprite_map['*t*'] = filetime
sprite_map['*s*'] = new_image.size

sizes = zip(files, sizes)
cache_tmp = tempfile.NamedTemporaryFile(delete=False, dir=ASSETS_ROOT)
Expand Down Expand Up @@ -469,7 +486,9 @@ def sprite_classes(map):
@register('sprite', 3)
@register('sprite', 4)
@register('sprite', 5)
def sprite(map, sprite, offset_x=None, offset_y=None, cache_buster=True):
@register('sprite', 6)
def sprite(map, sprite, offset_x=None, offset_y=None, use_percentages=False,
cache_buster=True):
"""
Returns the image and background position for use in a single shorthand
property
Expand All @@ -486,14 +505,20 @@ def sprite(map, sprite, offset_x=None, offset_y=None, cache_buster=True):
url = '%s%s' % (config.ASSETS_URL, sprite_map['*f*'])
if cache_buster:
url += '?_=%s' % sprite_map['*t*']
x = Number(offset_x or 0, 'px')
y = Number(offset_y or 0, 'px')
if not x.value or (x.value <= -1 or x.value >= 1) and not x.is_simple_unit('%'):
x -= Number(sprite[2], 'px')
if not y.value or (y.value <= -1 or y.value >= 1) and not y.is_simple_unit('%'):
y -= Number(sprite[3], 'px')
unit = '%' if use_percentages else 'px'
coors = [Number(offset_x or 0, unit), Number(offset_y or 0, unit)]
map_size = sprite_map['*s*']
for i, coor in enumerate(coors):
if not coor.value or (coor.value <= -1 or coor.value >= 1) \
and (coor.is_simple_unit('%') and unit == '%' or
coor.is_simple_unit('px') and unit == 'px'):
value = sprite[i + 2]
if use_percentages and value:
value = -100.0 * value / (map_size[i] - sprite[0][i])
coors[i] -= Number(value, unit)

url = "url(%s)" % escape(url)
return List([String.unquoted(url), x, y])
return List([String.unquoted(url)] + coors)
return List([Number(0), Number(0)])


Expand Down Expand Up @@ -530,7 +555,9 @@ def has_sprite(map, sprite):
@register('sprite-position', 2)
@register('sprite-position', 3)
@register('sprite-position', 4)
def sprite_position(map, sprite, offset_x=None, offset_y=None):
@register('sprite-position', 5)
def sprite_position(map, sprite, offset_x=None, offset_y=None,
use_percentages=False):
"""
Returns the position for the original image in the sprite.
This is suitable for use as a value to background-position.
Expand All @@ -544,23 +571,50 @@ def sprite_position(map, sprite, offset_x=None, offset_y=None):
elif not sprite:
log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True})
if sprite:
x = None
if offset_x is not None and not isinstance(offset_x, Number):
x = offset_x
if not x or x.value not in ('left', 'right', 'center'):
if x:
offset_x = None
x = Number(offset_x or 0, 'px')
if not x.value or (x.value <= -1 or x.value >= 1) and not x.is_simple_unit('%'):
x -= Number(sprite[2], 'px')
y = None
if offset_y is not None and not isinstance(offset_y, Number):
y = offset_y
if not y or y.value not in ('top', 'bottom', 'center'):
if y:
offset_y = None
y = Number(offset_y or 0, 'px')
if not y.value or (y.value <= -1 or y.value >= 1) and not y.is_simple_unit('%'):
y -= Number(sprite[3], 'px')
return List([x, y])
unit = '%' if use_percentages else 'px'
map_size = sprite_map['*s*']

coors = [offset_x, offset_y]
positions = (('left', 'right', 'center'), ('top', 'bottom', 'center'))

for i, coor in enumerate(coors):
c = None
if coor is not None and not isinstance(coor, Number):
c = coor
if not c or c.value not in positions[i]:
if c:
coor = None
c = Number(offset_x or 0, unit)
if not c.value or (c.value <= -1 or c.value >= 1) \
and (c.is_simple_unit('%') and unit == '%' or
c.is_simple_unit('px') and unit == 'px'):
value = sprite[i + 2]
if use_percentages and value:
value = -100.0 * value / (map_size[i] - sprite[0][i])
coors[i] = c - Number(value, unit)
else:
coors[i] = c
else:
coors[i] = None

return List(coors)
return List([Number(0), Number(0)])


@register('sprite-does-not-have-parent', 2)
def sprite_does_not_have_parent(map, sprite):
map = map.render()
sprite_map = sprite_maps.get(map)
sprite_name = String.unquoted(sprite).value
sprite = sprite_map and sprite_map.get(sprite_name)
# if there is no selector, the sprite does not have any parents
return Boolean(not sprite[4])


@register('sprite-has-selector', 3)
def sprite_has_selector(map, sprite, selector):
map = map.render()
sprite_map = sprite_maps.get(map)
sprite_name = String.unquoted(sprite).value
sprite = sprite_map and sprite_map.get(sprite_name + '_' + selector.value)
return Boolean(sprite)
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// This file comes from compass (compass-style.org) and is only used in sprite-import test

// Determines those states for which you want to enable magic sprite selectors
$sprite-selectors: hover, target, active !default;

// 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-background-position($map, $sprite, $offset-x: 0, $offset-y: 0) {
background-position: sprite-position($map, $sprite, $offset-x, $offset-y);
}


// Determines if you want to include magic selectors in your sprites
$disable-magic-sprite-selectors:false !default;

// 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-background-position($map, $sprite, $offset-x, $offset-y);
@if $dimensions {
@include sprite-dimensions($map, $sprite);
}
@if not $disable-magic-sprite-selectors {
@include sprite-selectors($map, $sprite, $sprite, $offset-x, $offset-y);
}
}

// Include the selectors for the `$sprite` given the `$map` and the
// `$full-sprite-name`
// @private
@mixin sprite-selectors($map, $sprite-name, $full-sprite-name, $offset-x: 0, $offset-y: 0) {
@each $selector in $sprite-selectors {
@if sprite_has_selector($map, $sprite-name, $selector) {
&:#{$selector}, &.#{$full-sprite-name}_#{$selector}, &.#{$full-sprite-name}-#{$selector} {
@include sprite-background-position($map, "#{$sprite-name}_#{$selector}", $offset-x, $offset-y);
}
}
}
}

// 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, $prefix: sprite-map-name($map), $offset-x: 0, $offset-y: 0) {
@each $sprite-name in $sprite-names {
@if sprite_does_not_have_parent($map, $sprite-name) {
$full-sprite-name: "#{$prefix}-#{$sprite-name}";
.#{$full-sprite-name} {
@if $base-class { @extend #{$base-class}; }
@include sprite($map, $sprite-name, $dimensions, $offset-x, $offset-y);
}
}
}
}
10 changes: 10 additions & 0 deletions scss/tests/files/kronuz/sprite-import.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url(static/assets/squares-frdGCNpwVAfawnKv35k2NA.png) no-repeat;
}
.squares-ten-by-ten {
background-position: 0px 0px;
}

.squares-twenty-by-twenty {
background-position: -10px 0px;
}
20 changes: 20 additions & 0 deletions scss/tests/files/kronuz/sprite-import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
Disables cache_buster on sprite_map for sprite_import test
"""

from scss import compiler, types

sprite_map_0 = compiler.sprite_map

def sprite_map_patch(g, **kwargs):
global sprite_map_0
kwargs.setdefault('cache_buster', types.Boolean(False))
return sprite_map_0(g, **kwargs)


def setUp():
compiler.sprite_map = sprite_map_patch


def tearDown():
compiler.sprite_map = sprite_map_0
4 changes: 4 additions & 0 deletions scss/tests/files/kronuz/sprite-import.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$squares-layout:horizontal;
@import "squares/*.png";

@include all-squares-sprites;
11 changes: 11 additions & 0 deletions scss/tests/files/kronuz/sprite-pos-percentage.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.mod {
background: url(static/assets/squares-nafpKaM4RlNNZcIbS0wv_g.png) no-repeat;
}
.mod.ten-by-ten {
background: url(static/assets/squares-nafpKaM4RlNNZcIbS0wv_g.png) 0% 0%;
}
.mod.twenty-by-twenty {
width: 20px;
height: 20px;
background-position: 0% 100%;
}
16 changes: 16 additions & 0 deletions scss/tests/files/kronuz/sprite-pos-percentage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@option style:legacy;

$images: sprite-map("squares/*.png", $cache-buster: false);

.mod {
background: $images;
&.ten-by-ten {
background: sprite($images, "ten-by-ten", $use-percentages: true, $cache-buster: false);
}
&.twenty-by-twenty {
$file: sprite-file($images, "twenty-by-twenty");
width: image-width($file);
height: image-height($file);
background-position: sprite-position($images, "twenty-by-twenty", $use-percentages: true);
}
}
Loading