def nth(lst, n): """ Return the Nth item in the string """ n = NumberValue(n).value lst = List(lst).value try: n = int(float(n)) - 1 n = n % len(lst) except: if n.lower() == 'first': n = 0 elif n.lower() == 'last': n = -1 try: ret = lst[n] except KeyError: lst = [v for k, v in sorted(lst.items()) if isinstance(k, int)] try: ret = lst[n] except: ret = '' return ret.__class__(ret)
def percentage(value): value = NumberValue(value) value.units = {'%': _units_weights.get('%', 1), '_': '%'} return value
def lightness(color): c = ColorValue(color).value h, l, s = colorsys.rgb_to_hls(c[0] / 255.0, c[1] / 255.0, c[2] / 255.0) ret = NumberValue(l) ret.units = {'%': _units_weights.get('%', 1), '_': '%'} return ret
def hue(color): c = ColorValue(color).value h, l, s = colorsys.rgb_to_hls(c[0] / 255.0, c[1] / 255.0, c[2] / 255.0) ret = NumberValue(h * 360.0) ret.units = {'deg': _units_weights.get('deg', 1), '_': 'deg'} return ret
def hue(color): c = ColorValue(color).value h, l, s = colorsys.rgb_to_hls(c[0] / 255.0, c[1] / 255.0, c[2] / 255.0) return NumberValue(h * 360, unit='deg')
def alpha(color): c = ColorValue(color).value return NumberValue(c[3])
def blue(color): c = ColorValue(color).value return NumberValue(c[2])
return _position(False, p) @register('opposite-position') def opposite_position(p): return _position(True, p) # ------------------------------------------------------------------------------ # Math @register('pi', 0) def pi(): return NumberValue(math.pi) COMPASS_HELPERS_LIBRARY.add(NumberValue.wrap_python_function(math.sin), 'sin', 1) COMPASS_HELPERS_LIBRARY.add(NumberValue.wrap_python_function(math.cos), 'cos', 1) COMPASS_HELPERS_LIBRARY.add(NumberValue.wrap_python_function(math.tan), 'tan', 1) # ------------------------------------------------------------------------------ # Fonts def _font_url(path, only_path=False, cache_buster=True, inline=False): filepath = StringValue(path).value path = None if callable(config.STATIC_ROOT): try: _file, _storage = list(config.STATIC_ROOT(filepath))[0] d_obj = _storage.modified_time(_file) filetime = int(time.mktime(d_obj.timetuple()))
def red(color): c = ColorValue(color).value return NumberValue(c[0])
def hsl(h, s, l, type='hsl'): return hsla(h, s, l, NumberValue(1), type)
# ------------------------------------------------------------------------------ # Number functions @register('percentage', 1) def percentage(value): if not isinstance(value, NumberValue): raise TypeError("Expected number, got %r" % (value, )) if not value.is_unitless: raise TypeError("Expected unitless number, got %r" % (value, )) return value * NumberValue(100, unit='%') CORE_LIBRARY.add(NumberValue.wrap_python_function(abs), 'abs', 1) CORE_LIBRARY.add(NumberValue.wrap_python_function(round), 'round', 1) CORE_LIBRARY.add(NumberValue.wrap_python_function(math.ceil), 'ceil', 1) CORE_LIBRARY.add(NumberValue.wrap_python_function(math.floor), 'floor', 1) # ------------------------------------------------------------------------------ # List functions def __parse_separator(separator, default_from=None): if separator is None: return None separator = StringValue(separator).value if separator == 'comma': return True elif separator == 'space': return False
def __radial_svg(color_stops, cx, cy, r): gradient = '<radialGradient id="grad" gradientUnits="userSpaceOnUse" cx="%s" cy="%s" r="%s">%s</radialGradient>' % ( to_str(NumberValue(cx)), to_str(NumberValue(cy)), to_str( NumberValue(r)), __color_stops_svg(color_stops)) return __svg_template(gradient)
def _linear_svg(color_stops, x1, y1, x2, y2): gradient = '<linearGradient id="grad" x1="%s" y1="%s" x2="%s" y2="%s">%s</linearGradient>' % ( to_str(NumberValue(x1)), to_str( NumberValue(y1)), to_str(NumberValue(x2)), to_str( NumberValue(y2)), __color_stops_svg(color_stops)) return __svg_template(gradient)
def __color_stops(percentages, *args): if len(args) == 1: if isinstance(args[0], (list, tuple, List)): list(args[0]) elif isinstance(args[0], (StringValue, six.string_types)): color_stops = [] colors = split_params(getattr(args[0], 'value', args[0])) for color in colors: color = color.strip() if color.startswith('color-stop('): s, c = split_params(color[11:].rstrip(')')) s = s.strip() c = c.strip() else: c, s = color.split() color_stops.append((to_float(s), c)) return color_stops colors = [] stops = [] prev_color = False for c in args: for c in List.from_maybe(c): if isinstance(c, ColorValue): if prev_color: stops.append(None) colors.append(c) prev_color = True elif isinstance(c, NumberValue): stops.append(c) prev_color = False if prev_color: stops.append(None) stops = stops[:len(colors)] if stops[0] is None: stops[0] = NumberValue(0, '%') if stops[-1] is None: stops[-1] = NumberValue(100, '%') if percentages: max_stops = max(s and (s.value if s.unit != '%' else None) or None for s in stops) else: max_stops = max(s if s and s.unit != '%' else None for s in stops) stops = [s / max_stops if s and s.unit != '%' else s for s in stops] init = 0 start = None for i, s in enumerate(stops + [1.0]): if s is None: if start is None: start = i end = i else: final = s if start is not None: stride = (final - init) / NumberValue(end - start + 1 + (1 if i < len(stops) else 0)) for j in range(start, end + 1): stops[j] = init + stride * NumberValue(j - start + 1) init = final start = None if not max_stops or percentages: pass else: stops = [s * max_stops for s in stops] return zip(stops, colors)
def grad_end_position(*color_stops): color_stops = __color_stops(False, *color_stops) return NumberValue(__grad_end_position(False, color_stops))
# ------------------------------------------------------------------------------ # Number functions @register('percentage', 1) def percentage(value): if not isinstance(value, NumberValue): raise TypeError("Expected number, got %r" % (value,)) if not value.is_unitless: raise TypeError("Expected unitless number, got %r" % (value,)) return value * NumberValue(100, unit='%') CORE_LIBRARY.add(NumberValue.wrap_python_function(abs), 'abs', 1) CORE_LIBRARY.add(NumberValue.wrap_python_function(round), 'round', 1) CORE_LIBRARY.add(NumberValue.wrap_python_function(math.ceil), 'ceil', 1) CORE_LIBRARY.add(NumberValue.wrap_python_function(math.floor), 'floor', 1) # ------------------------------------------------------------------------------ # List functions def __parse_separator(separator, default_from=None): if separator is None: return None separator = StringValue(separator).value if separator == 'comma': return True elif separator == 'space': return False
def green(color): c = ColorValue(color).value return NumberValue(c[1])
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. """ g = StringValue(g).value if not Image: raise Exception("Images manipulation require PIL") if g in sprite_maps: sprite_maps[glob]["*"] = datetime.datetime.now() elif ".." not in g: # Protect against going to prohibited places... vertical = kwargs.get("direction", "vertical") == "vertical" repeat = StringValue(kwargs.get("repeat", "no-repeat")) position = NumberValue(kwargs.get("position", 0)) collapse_x = NumberValue(kwargs.get("collapse_x", 0)) collapse_y = NumberValue(kwargs.get("collapse_y", 0)) if position and position > -1 and position < 1: position.units = {"%": _units_weights.get("%", 1), "_": "%"} dst_colors = kwargs.get("dst_color") if isinstance(dst_colors, ListValue): dst_colors = [list(ColorValue(v).value[:3]) for n, v in dst_colors.items() if v] else: dst_colors = [list(ColorValue(dst_colors).value[:3])] if dst_colors else [] src_colors = kwargs.get("src_color") if isinstance(src_colors, ListValue): src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) for n, v in src_colors.items()] else: src_colors = [tuple(ColorValue(src_colors).value[:3]) if src_colors else (0, 0, 0)] len_colors = max(len(dst_colors), len(src_colors)) dst_colors = (dst_colors * len_colors)[:len_colors] src_colors = (src_colors * len_colors)[:len_colors] spacing = kwargs.get("spacing", 0) if isinstance(spacing, ListValue): spacing = [int(NumberValue(v).value) for n, v in spacing.items()] else: spacing = [int(NumberValue(spacing).value)] spacing = (spacing * 4)[:4] if callable(config.STATIC_ROOT): glob_path = g rfiles = files = sorted(config.STATIC_ROOT(g)) else: glob_path = os.path.join(config.STATIC_ROOT, g) files = glob.glob(glob_path) files = sorted((f, None) for f in files) rfiles = [(f[len(config.STATIC_ROOT) :], s) for f, s in files] if not files: log.error("Nothing found at '%s'", glob_path) return StringValue(None) times = [] for file, storage in files: try: d_obj = storage.modified_time(file) times.append(int(time.mktime(d_obj.timetuple()))) except: times.append(int(os.path.getmtime(file))) map_name = os.path.normpath(os.path.dirname(g)).replace("\\", "_").replace("/", "_") key = list(zip(*files)[0]) + times + [repr(kwargs), config.ASSETS_URL] key = map_name + "-" + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip("=").replace("-", "_") asset_file = key + ".png" asset_path = os.path.join(config.ASSETS_ROOT, asset_file) try: asset, map, sizes = pickle.load(open(asset_path + ".cache")) sprite_maps[asset] = map except: def images(): for file, storage in files: yield Image.open(storage.open(file)) if storage is not None else Image.open(file) names = tuple(os.path.splitext(os.path.basename(file))[0] for file, storage in files) positions = [] spacings = [] tot_spacings = [] for name in names: name = name.replace("-", "_") _position = kwargs.get(name + "_position") if _position is None: _position = position else: _position = NumberValue(_position) if _position and _position > -1 and _position < 1: _position.units = {"%": _units_weights.get("%", 1), "_": "%"} positions.append(_position) _spacing = kwargs.get(name + "_spacing") if _spacing is None: _spacing = spacing else: if isinstance(_spacing, ListValue): _spacing = [int(NumberValue(v).value) for n, v in _spacing.items()] else: _spacing = [int(NumberValue(_spacing).value)] _spacing = (_spacing * 4)[:4] spacings.append(_spacing) if _position and _position.unit != "%": if vertical: if _position > 0: tot_spacings.append((_spacing[0], _spacing[1], _spacing[2], _spacing[3] + _position)) else: if _position > 0: tot_spacings.append((_spacing[0] + _position, _spacing[1], _spacing[2], _spacing[3])) else: tot_spacings.append(_spacing) sizes = tuple((collapse_x or image.size[0], collapse_y or image.size[1]) for image in images()) _spacings = zip(*tot_spacings) if vertical: width = max(zip(*sizes)[0]) + max(_spacings[1]) + max(_spacings[3]) height = sum(zip(*sizes)[1]) + sum(_spacings[0]) + sum(_spacings[2]) else: width = sum(zip(*sizes)[0]) + sum(_spacings[1]) + sum(_spacings[3]) height = max(zip(*sizes)[1]) + max(_spacings[0]) + max(_spacings[2]) new_image = Image.new(mode="RGBA", size=(width, height), color=(0, 0, 0, 0)) offsets_x = [] offsets_y = [] offset = 0 for i, image in enumerate(images()): spacing = spacings[i] position = positions[i] iwidth, iheight = image.size width, height = sizes[i] if vertical: if position and position.unit == "%": x = width * position.value - (spacing[3] + height + spacing[1]) elif position.value < 0: x = width + position.value - (spacing[3] + height + spacing[1]) else: x = position.value offset += spacing[0] for i, dst_color in enumerate(dst_colors): src_color = src_colors[i] pixdata = image.load() for _y in xrange(image.size[1]): for _x in xrange(image.size[0]): pixel = pixdata[_x, _y] if pixel[:3] == src_color: pixdata[_x, _y] = tuple( [int(c) for c in dst_color] + [pixel[3] if len(pixel) == 4 else 255] ) if iwidth != width or iheight != height: cy = 0 while cy < iheight: cx = 0 while cx < iwidth: cropped_image = image.crop((cx, cy, cx + width, cy + height)) new_image.paste(cropped_image, (int(x + spacing[3]), offset), cropped_image) cx += width cy += height else: new_image.paste(image, (int(x + spacing[3]), offset)) offsets_x.append(x) offsets_y.append(offset - spacing[0]) offset += height + spacing[2] else: if position and position.unit == "%": y = height * position.value - (spacing[0] + height + spacing[2]) elif position.value < 0: y = height + position.value - (spacing[0] + height + spacing[2]) else: y = position.value offset += spacing[3] for i, dst_color in enumerate(dst_colors): src_color = src_colors[i] pixdata = image.load() for _y in xrange(image.size[1]): for _x in xrange(image.size[0]): pixel = pixdata[_x, _y] if pixel[:3] == src_color: pixdata[_x, _y] = tuple( [int(c) for c in dst_color] + [pixel[3] if len(pixel) == 4 else 255] ) if iwidth != width or iheight != height: cy = 0 while cy < iheight: cx = 0 while cx < iwidth: cropped_image = image.crop((cx, cy, cx + width, cy + height)) new_image.paste(cropped_image, (offset, int(y + spacing[0])), cropped_image) cx += width cy += height else: new_image.paste(image, (offset, int(y + spacing[0]))) offsets_x.append(offset - spacing[3]) offsets_y.append(y) offset += width + spacing[1] try: new_image.save(asset_path) except IOError: log.exception("Error while saving image") filetime = int(time.mktime(datetime.datetime.now().timetuple())) url = "%s%s?_=%s" % (config.ASSETS_URL, asset_file, filetime) asset = 'url("%s") %s' % (escape(url), repeat) # Use the sorted list to remove older elements (keep only 500 objects): if len(sprite_maps) > 1000: for a in sorted(sprite_maps, key=lambda a: sprite_maps[a]["*"], reverse=True)[500:]: del sprite_maps[a] # Add the new object: map = dict(zip(names, zip(sizes, rfiles, offsets_x, offsets_y))) map["*"] = datetime.datetime.now() map["*f*"] = asset_file map["*k*"] = key map["*n*"] = map_name map["*t*"] = filetime tmp_dir = config.ASSETS_ROOT cache_tmp = tempfile.NamedTemporaryFile(delete=False, dir=tmp_dir) pickle.dump((asset, map, zip(files, sizes)), cache_tmp) cache_tmp.close() os.rename(cache_tmp.name, asset_path + ".cache") sprite_maps[asset] = map for file, size in sizes: _image_size_cache[file] = size ret = StringValue(asset) return ret