def __init__(self, tokens, type=None): self.tokens = tokens self.units = {} if tokens is None: self.value = 0.0 elif isinstance(tokens, NumberValue): self.value = tokens.value self.units = tokens.units.copy() if tokens.units: type = None elif isinstance(tokens, (StringValue, basestring)): tokens = getattr(tokens, "value", tokens) try: if tokens and tokens[-1] == "%": self.value = to_float(tokens[:-1]) / 100.0 self.units = {"%": _units_weights.get("%", 1), "_": "%"} else: self.value = to_float(tokens) except ValueError: raise ValueError("Value is not a Number! (%s)" % tokens) elif isinstance(tokens, (int, float)): # TODO i don't like this; should store the original and only divide # when converting. requires fixing __str__ though self.value = float(tokens) * _conv_factor.get(type, 1.0) else: raise ValueError("Can't convert to CSS number: %s" % repr(tokens)) if type is not None: self.units = {type: _units_weights.get(type, 1), "_": type}
def _do_op(cls, first, second, op): if op == operator.__add__ and isinstance(second, String): return String(first.render(), quotes=None) + second first_unit = first.unit second_unit = second.unit if op == operator.__add__ or op == operator.__sub__: if first_unit == "%" and not second_unit: second.units = {"%": _units_weights.get("%", 1), "_": "%"} second.value /= 100.0 elif first_unit == "%" and second_unit != "%": first = NumberValue(second) * first.value elif second_unit == "%" and not first_unit: first.units = {"%": _units_weights.get("%", 1), "_": "%"} first.value /= 100.0 elif second_unit == "%" and first_unit != "%": second = NumberValue(first) * second.value elif op == operator.__div__: if first_unit and first_unit == second_unit: first.units = {} second.units = {} val = op(first.value, second.value) ret = NumberValue(None).merge(first) ret = ret.merge(second) ret.value = val return ret
def __init__(self, tokens, type=None): self.tokens = tokens self.units = {} if tokens is None: self.value = 0.0 elif isinstance(tokens, ParserValue): self.value = float(tokens.value) elif isinstance(tokens, NumberValue): self.value = tokens.value self.units = tokens.units.copy() if tokens.units: type = None elif isinstance(tokens, (StringValue, basestring)): tokens = getattr(tokens, 'value', tokens) if _undefined_re.match(tokens): raise ValueError("Value is not a Number! (%s)" % tokens) try: if tokens and tokens[-1] == '%': self.value = to_float(tokens[:-1]) / 100.0 self.units = {'%': _units_weights.get('%', 1), '_': '%'} else: self.value = to_float(tokens) except ValueError: raise ValueError("Value is not a Number! (%s)" % tokens) elif isinstance(tokens, (int, float)): self.value = float(tokens) else: raise ValueError("Can't convert to CSS number: %r" % tokens) if type is not None: self.units = {type: _units_weights.get(type, 1), '_': type}
def merge(self, obj): obj = NumberValue(obj) self.value = obj.value for unit, val in obj.units.items(): if unit != "_": self.units.setdefault(unit, 0) self.units[unit] += val unit = obj.unit if _units_weights.get(self.units.get("_"), 1) <= _units_weights.get(unit, 1): self.units["_"] = unit return self
def convert_to(self, type): val = self.value if not self.unit: val *= _conv_factor.get(type, 1.0) ret = NumberValue(val) if type == 'deg' and ret.value > 360: ret.value = ret.value % 360.0 ret.units = {type: _units_weights.get(type, 1), '_': type} return ret
def unit(self): unit = "" if self.units: if "_" in self.units: units = self.units.copy() _unit = units.pop("_") units.setdefault(_unit, 0) units[_unit] += _units_weights.get(_unit, 1) # Give more weight to the first unit ever set else: units = self.units units = sorted(units, key=units.get) while len(units): unit = units.pop() if unit: break return unit
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 _do_op(cls, first, second, op): if isinstance(first, ListValue) and isinstance(second, ListValue): ret = ListValue(first) for k, v in ret.items(): try: ret.value[k] = op(ret.value[k], second.value[k]) except KeyError: pass return ret if isinstance(first, ListValue): ret = ListValue(first) for k, v in ret.items(): ret.value[k] = op(ret.value[k], second) return ret if isinstance(second, ListValue): ret = ListValue(second) for k, v in ret.items(): ret.value[k] = op(first, ret.value[k]) return ret if isinstance(first, basestring): first = StringValue(first) elif isinstance(first, (int, float)): first = NumberValue(first) if isinstance(second, basestring): second = StringValue(second) elif isinstance(second, (int, float)): second = NumberValue(second) if op in (operator.__div__, operator.__sub__): if isinstance(first, QuotedStringValue): first = NumberValue(first) if isinstance(second, QuotedStringValue): second = NumberValue(second) elif op == operator.__mul__: if isinstance(first, NumberValue) and isinstance(second, QuotedStringValue): first.value = int(first.value) val = op(second.value, first.value) return second.__class__(val) if isinstance(first, QuotedStringValue) and isinstance(second, NumberValue): second.value = int(second.value) val = op(first.value, second.value) return first.__class__(val) if not isinstance(first, NumberValue) or not isinstance(second, NumberValue): return op(first.value if isinstance(first, NumberValue) else first, second.value if isinstance(second, NumberValue) else second) first_unit = first.unit second_unit = second.unit if op == operator.__add__ or op == operator.__sub__: if first_unit == '%' and not second_unit: second.units = {'%': _units_weights.get('%', 1), '_': '%'} second.value /= 100.0 elif first_unit == '%' and second_unit != '%': first = NumberValue(second) * first.value elif second_unit == '%' and not first_unit: first.units = {'%': _units_weights.get('%', 1), '_': '%'} first.value /= 100.0 elif second_unit == '%' and first_unit != '%': second = NumberValue(first) * second.value val = op(first.value, second.value) ret = NumberValue(None).merge(first) ret = ret.merge(second) ret.value = val return ret
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