Exemplo n.º 1
0
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()))
            if inline:
                path = _storage.open(_file)
        except:
            filetime = 'NA'
    else:
        _path = os.path.join(config.STATIC_ROOT, filepath.strip('/'))
        if os.path.exists(_path):
            filetime = int(os.path.getmtime(_path))
            if inline:
                path = open(_path, 'rb')
        else:
            filetime = 'NA'
    BASE_URL = config.STATIC_URL

    if path and inline:
        mime_type = mimetypes.guess_type(filepath)[0]
        url = 'data:' + mime_type + ';base64,' + base64.b64encode(path.read())
    else:
        url = '%s%s' % (BASE_URL, filepath)
        if cache_buster:
            url = add_cache_buster(url, filetime)

    if not only_path:
        url = 'url("%s")' % escape(url)
    return StringValue(url)
Exemplo n.º 2
0
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()))
            if inline:
                path = _storage.open(_file)
        except:
            filetime = 'NA'
    else:
        _path = os.path.join(config.STATIC_ROOT, filepath.strip('/'))
        if os.path.exists(_path):
            filetime = int(os.path.getmtime(_path))
            if inline:
                path = open(_path, 'rb')
        else:
            filetime = 'NA'
    BASE_URL = config.STATIC_URL

    if path and inline:
        mime_type = mimetypes.guess_type(filepath)[0]
        url = 'data:' + mime_type + ';base64,' + base64.b64encode(path.read())
    else:
        url = '%s%s' % (BASE_URL, filepath)
        if cache_buster:
            url = add_cache_buster(url, filetime)

    if not only_path:
        url = 'url("%s")' % escape(url)
    return StringValue(url)
Exemplo n.º 3
0
def sprite(map, sprite, offset_x=None, offset_y=None, cache_buster=True):
    """
    Returns the image and background position for use in a single shorthand
    property
    """
    map = map.render()
    sprite_maps = _get_cache('sprite_maps')
    sprite_map = sprite_maps.get(map)
    sprite_name = String.unquoted(sprite).value
    sprite = sprite_map and sprite_map.get(sprite_name)
    if not sprite_map:
        log.error("No sprite map found: %s", map, extra={'stack': True})
    elif not sprite:
        log.error("No sprite found: %s in %s",
                  sprite_name,
                  sprite_map['*n*'],
                  extra={'stack': True})
    if sprite:
        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')
        url = "url(%s)" % escape(url)
        return List([String.unquoted(url), x, y])
    return List([Number(0), Number(0)])
Exemplo n.º 4
0
def sprite(map, sprite, offset_x=None, offset_y=None, cache_buster=True):
    """
    Returns the image and background position for use in a single shorthand
    property
    """
    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 not sprite_map:
        log.error("No sprite map found: %s", map, extra={'stack': True})
    elif not sprite:
        log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True})
    if sprite:
        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')
        url = "url(%s)" % escape(url)
        return List([String.unquoted(url), x, y])
    return List([Number(0), Number(0)])
Exemplo n.º 5
0
def background_noise(density=None,
                     opacity=None,
                     size=None,
                     monochrome=False,
                     intensity=(),
                     color=None,
                     background=None,
                     inline=False):
    if not Image:
        raise Exception("Images manipulation require PIL")

    density = [Number(v).value for v in List.from_maybe(density)]
    intensity = [Number(v).value for v in List.from_maybe(intensity)]
    color = [Color(v).value for v in List.from_maybe(color) if v]
    opacity = [Number(v).value for v in List.from_maybe(opacity)]

    size = int(Number(size).value) if size else 0
    if size < 1 or size > 512:
        size = 200

    monochrome = bool(monochrome)

    background = Color(background).value if background else None

    new_image = Image.new(mode='RGBA', size=(size, size))

    pixdata = new_image.load()
    _image_noise(pixdata, size, density, intensity, color, opacity, monochrome)

    if not inline:
        key = (size, density, intensity, color, opacity, monochrome)
        asset_file = 'noise-%s%sx%s' % ('mono-' if monochrome else '', size,
                                        size)
        # asset_file += '-[%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or []))
        asset_file += '-' + base64.urlsafe_b64encode(
            hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_')
        asset_file += '.png'
        asset_path = os.path.join(
            config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'),
            asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = six.BytesIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = 'data:image/png;base64,' + base64.b64encode(contents)

    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Exemplo n.º 6
0
def sprite_url(map):
    """
    Returns a url to the sprite image.
    """
    map = StringValue(map).value
    sprite_map = sprite_maps.get(map)
    if not sprite_map:
        log.error("No sprite map found: %s", map, extra={'stack': True})
    if sprite_map:
        url = '%s%s?_=%s' % (config.ASSETS_URL, sprite_map['*f*'], sprite_map['*t*'])
        url = "url(%s)" % escape(url)
        return StringValue(url)
    return StringValue(None)
Exemplo n.º 7
0
def _font_url(path, only_path=False, cache_buster=True, inline=False):
    filepath = String.unquoted(path).value
    file = None
    FONTS_ROOT = _fonts_root()
    if callable(FONTS_ROOT):
        try:
            _file, _storage = list(FONTS_ROOT(filepath))[0]
        except IndexError:
            filetime = None
        else:
            filetime = getmtime(_file, _storage)
        if filetime is None:
            filetime = 'NA'
        elif inline:
            file = _storage.open(_file)
    else:
        _path = os.path.join(FONTS_ROOT, filepath.strip('/'))
        filetime = getmtime(_path)
        if filetime is None:
            filetime = 'NA'
        elif inline:
            file = open(_path, 'rb')

    BASE_URL = config.FONTS_URL or config.STATIC_URL
    if file and inline:
        font_type = None
        if re.match(r'^([^?]+)[.](.*)([?].*)?$', path.value):
            font_type = String.unquoted(
                re.match(r'^([^?]+)[.](.*)([?].*)?$',
                         path.value).groups()[1]).value

        if not FONT_TYPES.get(font_type):
            raise Exception('Could not determine font type for "%s"' %
                            path.value)

        mime = FONT_TYPES.get(font_type)
        if font_type == 'woff':
            mime = 'application/font-woff'
        elif font_type == 'eot':
            mime = 'application/vnd.ms-fontobject'
        url = 'data:' + (mime if '/' in mime else 'font/%s' %
                         mime) + ';base64,' + base64.b64encode(file.read())
        file.close()
    else:
        url = '%s/%s' % (BASE_URL.rstrip('/'), filepath.lstrip('/'))
        if cache_buster and filetime != 'NA':
            url = add_cache_buster(url, filetime)

    if not only_path:
        url = 'url(%s)' % escape(url)
    return String.unquoted(url)
Exemplo n.º 8
0
def linear_svg_gradient(*args):
    args = List.from_maybe_starargs(args)
    color_stops = args
    start = None
    if isinstance(args[-1], (String, Number, six.string_types)):
        start = args[-1]
        color_stops = args[:-1]
    color_stops = __color_stops(False, *color_stops)
    x1, y1 = zip(*grad_point(start).items())[1]
    x2, y2 = zip(*grad_point(opposite_position(start)).items())[1]
    svg = _linear_svg(color_stops, x1, y1, x2, y2)
    url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg)
    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Exemplo n.º 9
0
def background_brushed(density=None, intensity=None, color=None, opacity=None, size=None, monochrome=False, direction=(), spread=(), background=None, inline=False):
    if not Image:
        raise Exception("Images manipulation require PIL")

    density = [Number(v).value for v in List.from_maybe(density)]
    intensity = [Number(v).value for v in List.from_maybe(intensity)]
    color = [Color(v).value for v in List.from_maybe(color) if v]
    opacity = [Number(v).value for v in List.from_maybe(opacity)]

    size = int(Number(size).value) if size else -1
    if size < 0 or size > 512:
        size = 200

    monochrome = bool(monochrome)

    direction = [Number(v).value for v in List.from_maybe(direction)]
    spread = [Number(v).value for v in List.from_maybe(spread)]

    background = Color(background).value if background else None

    new_image = Image.new(
        mode='RGBA',
        size=(size, size)
    )

    pixdata = new_image.load()
    _image_brushed(pixdata, size, density, intensity, color, opacity, monochrome, direction, spread, background)

    if not inline:
        key = (size, density, intensity, color, opacity, monochrome, direction, spread, background)
        asset_file = 'brushed-%s%sx%s' % ('mono-' if monochrome else '', size, size)
        # asset_file += '-[%s][%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or []), '-'.join(to_str(s).replace('.', '_') for s in direction or []))
        asset_file += '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_')
        asset_file += '.png'
        asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = six.BytesIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = 'data:image/png;base64,' + base64.b64encode(contents)

    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Exemplo n.º 10
0
def radial_svg_gradient(*args):
    args = List.from_maybe_starargs(args)
    color_stops = args
    center = None
    if isinstance(args[-1], (String, Number)):
        center = args[-1]
        color_stops = args[:-1]
    color_stops = __color_stops(False, *color_stops)
    cx, cy = grad_point(center)
    r = __grad_end_position(True, color_stops)
    svg = __radial_svg(color_stops, cx, cy, r)
    url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg)
    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Exemplo n.º 11
0
def linear_svg_gradient(*args):
    args = List.from_maybe_starargs(args)
    color_stops = args
    start = None
    if isinstance(args[-1], (String, Number)):
        start = args[-1]
        color_stops = args[:-1]
    color_stops = __color_stops(False, *color_stops)
    x1, y1 = grad_point(start)
    x2, y2 = grad_point(opposite_position(start))
    svg = _linear_svg(color_stops, x1, y1, x2, y2)
    url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg)
    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Exemplo n.º 12
0
def linear_svg_gradient(*args):
    args = List.from_maybe_starargs(args)
    color_stops = args
    start = None
    if isinstance(args[-1], (StringValue, NumberValue, six.string_types)):
        start = args[-1]
        color_stops = args[:-1]
    color_stops = __color_stops(False, *color_stops)
    x1, y1 = zip(*grad_point(start).items())[1]
    x2, y2 = zip(*grad_point(opposite_position(start)).items())[1]
    svg = _linear_svg(color_stops, x1, y1, x2, y2)
    url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg)
    inline = 'url("%s")' % escape(url)
    return StringValue(inline)
Exemplo n.º 13
0
def background_brushed(density=None, intensity=None, color=None, opacity=None, size=None, monochrome=False, direction=(), spread=(), background=None, inline=False):
    if not Image:
        raise Exception("Images manipulation require PIL")

    density = [Number(v).value for v in List.from_maybe(density)]
    intensity = [Number(v).value for v in List.from_maybe(intensity)]
    color = [Color(v).value for v in List.from_maybe(color) if v]
    opacity = [Number(v).value for v in List.from_maybe(opacity)]

    size = int(Number(size).value) if size else -1
    if size < 0 or size > 512:
        size = 200

    monochrome = bool(monochrome)

    direction = [Number(v).value for v in List.from_maybe(direction)]
    spread = [Number(v).value for v in List.from_maybe(spread)]

    background = Color(background).value if background else None

    new_image = Image.new(
        mode='RGBA',
        size=(size, size)
    )

    pixdata = new_image.load()
    _image_brushed(pixdata, size, density, intensity, color, opacity, monochrome, direction, spread, background)

    if not inline:
        key = (size, density, intensity, color, opacity, monochrome, direction, spread, background)
        asset_file = 'brushed-%s%sx%s' % ('mono-' if monochrome else '', size, size)
        # asset_file += '-[%s][%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or []), '-'.join(to_str(s).replace('.', '_') for s in direction or []))
        asset_file += '-' + make_filename_hash(key)
        asset_file += '.png'
        asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = six.BytesIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = make_data_url('image/png', contents)

    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Exemplo n.º 14
0
def radial_svg_gradient(*args):
    args = List.from_maybe_starargs(args)
    color_stops = args
    center = None
    if isinstance(args[-1], (String, Number, six.string_types)):
        center = args[-1]
        color_stops = args[:-1]
    color_stops = __color_stops(False, *color_stops)
    cx, cy = zip(*grad_point(center).items())[1]
    r = __grad_end_position(True, color_stops)
    svg = __radial_svg(color_stops, cx, cy, r)
    url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg)
    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Exemplo n.º 15
0
def _font_url(path, only_path=False, cache_buster=True, inline=False):
    filepath = String.unquoted(path).value
    file = None
    FONTS_ROOT = _fonts_root()
    if callable(FONTS_ROOT):
        try:
            _file, _storage = list(FONTS_ROOT(filepath))[0]
        except IndexError:
            filetime = None
        else:
            filetime = getmtime(_file, _storage)
        if filetime is None:
            filetime = 'NA'
        elif inline:
            file = _storage.open(_file)
    else:
        _path = os.path.join(FONTS_ROOT, filepath.strip('/'))
        filetime = getmtime(_path)
        if filetime is None:
            filetime = 'NA'
        elif inline:
            file = open(_path, 'rb')

    BASE_URL = config.FONTS_URL or config.STATIC_URL
    if file and inline:
        font_type = None
        if re.match(r'^([^?]+)[.](.*)([?].*)?$', path.value):
            font_type = String.unquoted(re.match(r'^([^?]+)[.](.*)([?].*)?$', path.value).groups()[1]).value

        if not FONT_TYPES.get(font_type):
            raise Exception('Could not determine font type for "%s"' % path.value)

        mime = FONT_TYPES.get(font_type)
        if font_type == 'woff':
            mime = 'application/font-woff'
        elif font_type == 'eot':
            mime = 'application/vnd.ms-fontobject'
        url = make_data_url(
            (mime if '/' in mime else 'font/%s' % mime),
            file.read())
        file.close()
    else:
        url = '%s/%s' % (BASE_URL.rstrip('/'), filepath.lstrip('/'))
        if cache_buster and filetime != 'NA':
            url = add_cache_buster(url, filetime)

    if not only_path:
        url = 'url(%s)' % escape(url)
    return String.unquoted(url)
Exemplo n.º 16
0
def linear_svg_gradient(*args):
    if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)):
        args = ListValue(args[0]).values()
    color_stops = args
    start = None
    if isinstance(args[-1], (StringValue, NumberValue, basestring)):
        start = args[-1]
        color_stops = args[:-1]
    color_stops = __color_stops(False, *color_stops)
    x1, y1 = zip(*grad_point(start).items())[1]
    x2, y2 = zip(*grad_point(opposite_position(start)).items())[1]
    svg = _linear_svg(color_stops, x1, y1, x2, y2)
    url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg)
    inline = 'url("%s")' % escape(url)
    return StringValue(inline)
Exemplo n.º 17
0
def sprite_url(map, cache_buster=True):
    """
    Returns a url to the sprite image.
    """
    map = map.render()
    sprite_map = sprite_maps.get(map)
    if not sprite_map:
        log.error("No sprite map found: %s", map, extra={'stack': True})
    if sprite_map:
        url = '%s%s' % (config.ASSETS_URL, sprite_map['*f*'])
        if cache_buster:
            url += '?_=%s' % sprite_map['*t*']
        url = "url(%s)" % escape(url)
        return String.unquoted(url)
    return String.unquoted('')
Exemplo n.º 18
0
def radial_svg_gradient(*args):
    if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)):
        args = ListValue(args[0]).values()
    color_stops = args
    center = None
    if isinstance(args[-1], (StringValue, NumberValue, basestring)):
        center = args[-1]
        color_stops = args[:-1]
    color_stops = __color_stops(False, *color_stops)
    cx, cy = zip(*grad_point(center).items())[1]
    r = __grad_end_position(True, color_stops)
    svg = __radial_svg(color_stops, cx, cy, r)
    url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg)
    inline = 'url("%s")' % escape(url)
    return StringValue(inline)
Exemplo n.º 19
0
def sprite_url(map, cache_buster=True):
    """
    Returns a url to the sprite image.
    """
    map = map.render()
    sprite_map = sprite_maps.get(map)
    if not sprite_map:
        log.error("No sprite map found: %s", map, extra={'stack': True})
    if sprite_map:
        url = '%s%s' % (config.ASSETS_URL, sprite_map['*f*'])
        if cache_buster:
            url += '?_=%s' % sprite_map['*t*']
        url = "url(%s)" % escape(url)
        return String.unquoted(url)
    return String.unquoted('')
Exemplo n.º 20
0
def background_noise(density=None, opacity=None, size=None, monochrome=False, intensity=(), color=None, background=None, inline=False):
    if not Image:
        raise Exception("Images manipulation require PIL")

    density = [Number(v).value for v in List.from_maybe(density)]
    intensity = [Number(v).value for v in List.from_maybe(intensity)]
    color = [Color(v).value for v in List.from_maybe(color) if v]
    opacity = [Number(v).value for v in List.from_maybe(opacity)]

    size = int(Number(size).value) if size else 0
    if size < 1 or size > 512:
        size = 200

    monochrome = bool(monochrome)

    background = Color(background).value if background else None

    new_image = Image.new(
        mode='RGBA',
        size=(size, size)
    )

    pixdata = new_image.load()
    _image_noise(pixdata, size, density, intensity, color, opacity, monochrome)

    if not inline:
        key = (size, density, intensity, color, opacity, monochrome)
        asset_file = 'noise-%s%sx%s' % ('mono-' if monochrome else '', size, size)
        # asset_file += '-[%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or []))
        asset_file += '-' + make_filename_hash(key)
        asset_file += '.png'
        asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = six.BytesIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = make_data_url('image/png', contents)

    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Exemplo n.º 21
0
def _font_url(path, only_path=False, cache_buster=True, inline=False):
    filepath = String.unquoted(path).value
    file = None
    if callable(config.FONTS_ROOT):
        try:
            _file, _storage = list(config.FONTS_ROOT(filepath))[0]
            d_obj = _storage.modified_time(_file)
            filetime = int(time.mktime(d_obj.timetuple()))
            if inline:
                file = _storage.open(_file)
        except:
            filetime = 'NA'
    else:
        _path = os.path.join(config.FONTS_ROOT, filepath.strip('/'))
        if os.path.exists(_path):
            filetime = int(os.path.getmtime(_path))
            if inline:
                file = open(_path, 'rb')
        else:
            filetime = 'NA'
    BASE_URL = config.FONTS_URL

    if file and inline:
#        mime_type = mimetypes.guess_type(filepath)[0]
        font_type = None
        if re.match(r'^([^?]+)[.](.*)([?].*)?$', path.value):
            font_type = String.unquoted(re.match(r'^([^?]+)[.](.*)([?].*)?$', path.value).groups()[1]).value
                 
        if not FONT_TYPES.get(font_type):
            raise Exception('Could not determine font type for "%s"' % path.value)
        
        mime = FONT_TYPES.get(font_type)
        if font_type == 'woff':
            mime = 'application/font-woff'
        elif font_type=='eot':
            mime = 'application/vnd.ms-fontobject'
        url = 'data:' + (mime if '/' in mime else 'font/%s' % mime) + ';base64,' + base64.b64encode(file.read())
        file.close()
    else:
        url = '%s/%s' % (BASE_URL.rstrip('/'), filepath.lstrip('/'))
        if cache_buster and filetime != 'NA':
            url = add_cache_buster(url, filetime)

    if not only_path:
        url = 'url(%s)' % escape(url)
    return String.unquoted(url)
Exemplo n.º 22
0
def image_color(color, width=1, height=1):
    if not Image:
        raise Exception("Images manipulation require PIL")
    w = int(Number(width).value)
    h = int(Number(height).value)
    if w <= 0 or h <= 0:
        raise ValueError
    new_image = Image.new(
        mode='RGB' if color.alpha == 1 else 'RGBA',
        size=(w, h),
        color=color.rgba255,
    )
    output = six.BytesIO()
    new_image.save(output, format='PNG')
    contents = output.getvalue()
    output.close()
    url = make_data_url('image/png', contents)
    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Exemplo n.º 23
0
def image_color(color, width=1, height=1):
    if not Image:
        raise Exception("Images manipulation require PIL")
    c = ColorValue(color).value
    w = int(NumberValue(width).value)
    h = int(NumberValue(height).value)
    if w <= 0 or h <= 0:
        raise ValueError
    new_image = Image.new(
        mode='RGB' if c[3] == 1 else 'RGBA',
        size=(w, h),
        color=(c[0], c[1], c[2], int(c[3] * 255.0))
    )
    output = StringIO()
    new_image.save(output, format='PNG')
    contents = output.getvalue()
    output.close()
    mime_type = 'image/png'
    url = 'data:' + mime_type + ';base64,' + base64.b64encode(contents)
    inline = 'url("%s")' % escape(url)
    return StringValue(inline)
Exemplo n.º 24
0
def font_url(sheet, type_, only_path=False, cache_buster=True):
    font_sheet = font_sheets.get(sheet.render())
    type_ = String.unquoted(type_).render()
    if font_sheet:
        asset_files = font_sheet['*f*']
        asset_file = asset_files.get(type_)
        if asset_file:
            url = '%s%s' % (config.ASSETS_URL, asset_file)
            params = []
            # if type_ == 'eot':
            #     params.append('#iefix')
            if cache_buster:
                params.append('v=%s' % font_sheet['*t*'])
            if type_ == 'svg':
                params.append('#' + font_sheet['*n*'])
            if params:
                url += '?' + '&'.join(params)
            if not only_path:
                url = "url('%s')" % escape(url)
            return String.unquoted(url)
    return String.unquoted('')
Exemplo n.º 25
0
def image_color(color, width=1, height=1):
    if not Image:
        raise Exception("Images manipulation require PIL")
    c = ColorValue(color).value
    w = int(NumberValue(width).value)
    h = int(NumberValue(height).value)
    if w <= 0 or h <= 0:
        raise ValueError
    new_image = Image.new(
        mode='RGB' if c[3] == 1 else 'RGBA',
        size=(w, h),
        color=(c[0], c[1], c[2], int(c[3] * 255.0))
    )
    output = six.BytesIO()
    new_image.save(output, format='PNG')
    contents = output.getvalue()
    output.close()
    mime_type = 'image/png'
    url = 'data:' + mime_type + ';base64,' + base64.b64encode(contents)
    inline = 'url("%s")' % escape(url)
    return StringValue(inline)
Exemplo n.º 26
0
def font_url(sheet, type_, only_path=False, cache_buster=True):
    font_sheet = font_sheets.get(sheet.render())
    type_ = String.unquoted(type_).render()
    if font_sheet:
        asset_files = font_sheet['*f*']
        asset_file = asset_files.get(type_)
        if asset_file:
            url = '%s%s' % (config.ASSETS_URL, asset_file)
            params = []
            # if type_ == 'eot':
            #     params.append('#iefix')
            if cache_buster:
                params.append('v=%s' % font_sheet['*t*'])
            if type_ == 'svg':
                params.append('#' + font_sheet['*n*'])
            if params:
                url += '?' + '&'.join(params)
            if not only_path:
                url = "url('%s')" % escape(url)
            return String.unquoted(url)
    return String.unquoted('')
Exemplo n.º 27
0
def sprite(map, sprite, offset_x=None, offset_y=None):
    """
    Returns the image and background position for use in a single shorthand
    property
    """
    map = StringValue(map).value
    sprite_name = StringValue(sprite).value
    sprite_map = sprite_maps.get(map)
    sprite = sprite_map and sprite_map.get(sprite_name)
    if not sprite_map:
        log.error("No sprite map found: %s", map, extra={'stack': True})
    elif not sprite:
        log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True})
    if sprite:
        url = '%s%s?_=%s' % (config.ASSETS_URL, sprite_map['*f*'], sprite_map['*t*'])
        x = NumberValue(offset_x or 0, 'px')
        y = NumberValue(offset_y or 0, 'px')
        if not x or (x <= -1 or x >= 1) and x.unit != '%':
            x -= sprite[2]
        if not y or (y <= -1 or y >= 1) and y.unit != '%':
            y -= sprite[3]
        pos = "url(%s) %s %s" % (escape(url), x, y)
        return StringValue(pos)
    return StringValue('0 0')
Exemplo n.º 28
0
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`.

    $position - For `horizontal` and `vertical` directions, the position of the sprite. (defaults to `0`)
    $<sprite>-position - Position of a given sprite.

    $padding, $spacing - Adds paddings to sprites (top, right, bottom, left). (defaults to `0, 0, 0, 0`)
    $<sprite>-padding, $<sprite>-spacing - Padding for a given sprite.

    $dst-color - Together with `$src-color`, forms a map of source colors to be converted to destiny colors (same index of `$src-color` changed to `$dst-color`).
    $<sprite>-dst-color - Destiny colors for a given sprite. (defaults to `$dst-color`)

    $src-color - Selects source colors to be converted to the corresponding destiny colors. (defaults to `black`)
    $<sprite>-dst-color - Source colors for a given sprite. (defaults to `$src-color`)

    $collapse - Collapses every image in the sprite map to a fixed size (`x` and `y`).
    $collapse-x  - Collapses a size for `x`.
    $collapse-y  - Collapses a size for `y`.
    """
    if not Image:
        raise Exception("Images manipulation require PIL")

    sprite_maps = _get_cache('sprite_maps')

    now_time = time.time()

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

    _k_ = ','.join(globs)

    files = None
    rfiles = None
    tfiles = None
    map_name = None

    if _k_ in sprite_maps:
        sprite_maps[_k_]['*'] = now_time
    else:
        files = []
        rfiles = []
        tfiles = []
        for _glob in globs:
            if '..' not in _glob:  # Protect against going to prohibited places...
                if callable(config.STATIC_ROOT):
                    _glob_path = _glob
                    _rfiles = _files = sorted(config.STATIC_ROOT(_glob))
                else:
                    _glob_path = os.path.join(config.STATIC_ROOT, _glob)
                    _files = glob.glob(_glob_path)
                    _files = sorted((f, None) for f in _files)
                    _rfiles = [(rf[len(config.STATIC_ROOT):], s) for rf, s in _files]
                if _files:
                    files.extend(_files)
                    rfiles.extend(_rfiles)
                    base_name = os.path.normpath(os.path.dirname(_glob)).replace('\\', '_').replace('/', '_')
                    _map_name, _, _map_type = base_name.partition('.')
                    if _map_type:
                        _map_type += '-'
                    if not map_name:
                        map_name = _map_name
                    tfiles.extend([_map_type] * len(_files))
                else:
                    glob_path = _glob_path

    if files is not None:
        if not files:
            log.error("Nothing found at '%s'", glob_path)
            return String.unquoted('')

        key = [f for (f, s) in files] + [repr(kwargs), config.ASSETS_URL]
        key = map_name + '-' + make_filename_hash(key)
        asset_file = key + '.png'
        ASSETS_ROOT = _assets_root()
        asset_path = os.path.join(ASSETS_ROOT, asset_file)
        cache_path = os.path.join(config.CACHE_ROOT or ASSETS_ROOT, asset_file + '.cache')

        inline = Boolean(kwargs.get('inline', False))

        sprite_map = None
        asset = None
        file_asset = None
        inline_asset = None
        if os.path.exists(asset_path) or inline:
            try:
                save_time, file_asset, inline_asset, sprite_map, sizes = pickle.load(open(cache_path))
                if file_asset:
                    sprite_maps[file_asset.render()] = sprite_map
                if inline_asset:
                    sprite_maps[inline_asset.render()] = sprite_map
                if inline:
                    asset = inline_asset
                else:
                    asset = file_asset
            except:
                pass

            if sprite_map:
                for file_, storage in files:
                    _time = getmtime(file_, storage)
                    if save_time < _time:
                        if _time > now_time:
                            log.warning("File '%s' has a date in the future (cache ignored)" % file_)
                        sprite_map = None  # Invalidate cached sprite map
                        break

        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
            repeat = String.unquoted(kwargs.get('repeat', 'no-repeat')).value
            collapse = kwargs.get('collapse', Number(0))
            if isinstance(collapse, List):
                collapse_x = int(Number(collapse[0]).value)
                collapse_y = int(Number(collapse[-1]).value)
            else:
                collapse_x = collapse_y = int(Number(collapse).value)
            if 'collapse_x' in kwargs:
                collapse_x = int(Number(kwargs['collapse_x']).value)
            if 'collapse_y' in kwargs:
                collapse_y = int(Number(kwargs['collapse_y']).value)

            position = Number(kwargs.get('position', 0))
            if not position.is_simple_unit('%') and position.value > 1:
                position = position.value / 100.0
            else:
                position = position.value
            if position < 0:
                position = 0.0
            elif position > 1:
                position = 1.0

            padding = kwargs.get('padding', kwargs.get('spacing', Number(0)))
            padding = [int(Number(v).value) for v in List.from_maybe(padding)]
            padding = (padding * 4)[:4]

            dst_colors = kwargs.get('dst_color')
            dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(dst_colors) if v]
            src_colors = kwargs.get('src_color', Color.from_name('black'))
            src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(src_colors)]
            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]

            def images(f=lambda x: x):
                for file_, storage in f(files):
                    if storage is not None:
                        _file = storage.open(file_)
                    else:
                        _file = file_
                    _image = Image.open(_file)
                    yield _image

            names = tuple(os.path.splitext(os.path.basename(file_))[0] for file_, storage in files)
            tnames = tuple(tfiles[i] + n for i, n in enumerate(names))

            has_dst_colors = False
            all_dst_colors = []
            all_src_colors = []
            all_positions = []
            all_paddings = []

            for name in names:
                name = name.replace('-', '_')

                _position = kwargs.get(name + '_position')
                if _position is None:
                    _position = position
                else:
                    _position = Number(_position)
                    if not _position.is_simple_unit('%') and _position.value > 1:
                        _position = _position.value / 100.0
                    else:
                        _position = _position.value
                    if _position < 0:
                        _position = 0.0
                    elif _position > 1:
                        _position = 1.0
                all_positions.append(_position)

                _padding = kwargs.get(name + '_padding', kwargs.get(name + '_spacing'))
                if _padding is None:
                    _padding = padding
                else:
                    _padding = [int(Number(v).value) for v in List.from_maybe(_padding)]
                    _padding = (_padding * 4)[:4]
                all_paddings.append(_padding)

                _dst_colors = kwargs.get(name + '_dst_color')
                if _dst_colors is None:
                    _dst_colors = dst_colors
                    if dst_colors:
                        has_dst_colors = True
                else:
                    has_dst_colors = True
                    _dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(_dst_colors) if v]
                _src_colors = kwargs.get(name + '_src_color', Color.from_name('black'))
                if _src_colors is None:
                    _src_colors = src_colors
                else:
                    _src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(_src_colors)]
                _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]
                all_dst_colors.append(_dst_colors)
                all_src_colors.append(_src_colors)

            sizes = tuple((collapse_x or i.size[0], collapse_y or i.size[1]) for i in images())

            if direction == 'horizontal':
                layout = HorizontalSpritesLayout(sizes, all_paddings, position=all_positions)
            elif direction == 'vertical':
                layout = VerticalSpritesLayout(sizes, all_paddings, position=all_positions)
            elif direction == 'diagonal':
                layout = DiagonalSpritesLayout(sizes, all_paddings)
            elif direction == 'smart':
                layout = PackedSpritesLayout(sizes, all_paddings)
            else:
                raise Exception("Invalid direction %r" % (direction,))
            layout_positions = list(layout)

            new_image = Image.new(
                mode='RGBA',
                size=(layout.width, layout.height),
                color=(0, 0, 0, 0)
            )

            useless_dst_color = has_dst_colors

            offsets_x = []
            offsets_y = []
            for i, image in enumerate(images()):
                x, y, width, height, cssx, cssy, cssw, cssh = layout_positions[i]
                iwidth, iheight = image.size

                if has_dst_colors:
                    pixdata = image.load()
                    for _y in xrange(iheight):
                        for _x in xrange(iwidth):
                            pixel = pixdata[_x, _y]
                            a = pixel[3] if len(pixel) == 4 else 255
                            if a:
                                rgb = pixel[:3]
                                for j, dst_color in enumerate(all_dst_colors[i]):
                                    if rgb == all_src_colors[i][j]:
                                        new_color = tuple([int(c) for c in dst_color] + [a])
                                        if pixel != new_color:
                                            pixdata[_x, _y] = new_color
                                            useless_dst_color = False
                                        break

                if iwidth != width or iheight != height:
                    cy = 0
                    while cy < iheight:
                        cx = 0
                        while cx < iwidth:
                            new_image = alpha_composite(new_image, image, (x, y), (cx, cy, cx + width, cy + height))
                            cx += width
                        cy += height
                else:
                    new_image.paste(image, (x, y))
                offsets_x.append(cssx)
                offsets_y.append(cssy)

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

            filetime = int(now_time)

            if not inline:
                try:
                    new_image.save(asset_path)
                    url = '%s%s' % (config.ASSETS_URL, asset_file)
                    if cache_buster:
                        url += '?_=%s' % filetime
                except IOError:
                    log.exception("Error while saving image")
                    inline = True
            if inline:
                output = six.BytesIO()
                new_image.save(output, format='PNG')
                contents = output.getvalue()
                output.close()
                mime_type = 'image/png'
                url = make_data_url(mime_type, contents)

            url = 'url(%s)' % escape(url)
            if inline:
                asset = inline_asset = List([String.unquoted(url), String.unquoted(repeat)])
            else:
                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['*'] = now_time
            sprite_map['*f*'] = asset_file
            sprite_map['*k*'] = key
            sprite_map['*n*'] = map_name
            sprite_map['*t*'] = filetime

            sizes = zip(files, sizes)
            cache_tmp = tempfile.NamedTemporaryFile(delete=False, dir=ASSETS_ROOT)
            pickle.dump((now_time, file_asset, inline_asset, sprite_map, sizes), cache_tmp)
            cache_tmp.close()
            if sys.platform == 'win32' and os.path.isfile(cache_path):
                # on windows, cannot rename a file to a path that matches
                # an existing file, we have to remove it first
                os.remove(cache_path)
            os.rename(cache_tmp.name, cache_path)

            # Use the sorted list to remove older elements (keep only 500 objects):
            if len(sprite_maps) > MAX_SPRITE_MAPS:
                for a in sorted(sprite_maps, key=lambda a: sprite_maps[a]['*'], reverse=True)[KEEP_SPRITE_MAPS:]:
                    del sprite_maps[a]
                log.warning("Exceeded maximum number of sprite maps (%s)" % MAX_SPRITE_MAPS)
            sprite_maps[asset.render()] = sprite_map
        image_size_cache = _get_cache('image_size_cache')
        for file_, size in sizes:
            image_size_cache[file_] = size
    # TODO this sometimes returns an empty list, or is never assigned to
    return asset
Exemplo n.º 29
0
def font_sheet(g, **kwargs):
    if not fontforge:
        raise Exception("Fonts manipulation require fontforge")

    now_time = time.time()

    g = String(g, quotes=None).value

    if g in font_sheets:
        font_sheets[glob]['*'] = now_time
    elif '..' not in g:  # Protect against going to prohibited places...
        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 = [(rf[len(config.STATIC_ROOT):], s) for rf, s in files]

        if not files:
            log.error("Nothing found at '%s'", glob_path)
            return String.unquoted('')

        glyph_name = os.path.normpath(os.path.dirname(g)).replace('\\', '_').replace('/', '_')
        key = [f for (f, s) in files] + [repr(kwargs), config.ASSETS_URL]
        key = glyph_name + '-' + make_filename_hash(key)
        asset_files = {
            'ttf': key + '.ttf',
            'svg': key + '.svg',
            'woff': key + '.woff',
            'eot': key + '.eot',
        }
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets')
        asset_paths = dict((type_, os.path.join(ASSETS_ROOT, asset_file)) for type_, asset_file in asset_files.items())
        cache_path = os.path.join(config.CACHE_ROOT or ASSETS_ROOT, key + '.cache')

        inline = Boolean(kwargs.get('inline', False))

        font_sheet = None
        asset = None
        file_assets = {}
        inline_assets = {}
        if all(os.path.exists(asset_path) for asset_path in asset_paths.values()) or inline:
            try:
                save_time, file_assets, inline_assets, font_sheet, codepoints = pickle.load(open(cache_path))
                if file_assets:
                    file_asset = List([file_asset for file_asset in file_assets.values()], separator=",")
                    font_sheets[file_asset.render()] = font_sheet
                if inline_assets:
                    inline_asset = List([inline_asset for inline_asset in inline_assets.values()], separator=",")
                    font_sheets[inline_asset.render()] = font_sheet
                if inline:
                    asset = inline_asset
                else:
                    asset = file_asset
            except:
                pass

            if font_sheet:
                for file_, storage in files:
                    _time = getmtime(file_, storage)
                    if save_time < _time:
                        if _time > now_time:
                            log.warning("File '%s' has a date in the future (cache ignored)" % file_)
                        font_sheet = None  # Invalidate cached custom font
                        break

        if font_sheet is None or asset is None:
            cache_buster = Boolean(kwargs.get('cache_buster', True))
            autowidth = Boolean(kwargs.get('autowidth', True))
            autohint = Boolean(kwargs.get('autohint', True))

            font = fontforge.font()
            font.encoding = 'UnicodeFull'
            font.design_size = 16
            font.em = 512
            font.ascent = 448
            font.descent = 64
            font.fontname = glyph_name
            font.familyname = glyph_name
            font.fullname = glyph_name
            if autowidth:
                font.autoWidth(0, 0, 512)
            if autohint:
                font.autoHint()

            def glyphs(f=lambda x: x):
                for file_, storage in f(files):
                    if storage is not None:
                        _file = storage.open(file_)
                    else:
                        _file = open(file_)
                    svgtext = _file.read()
                    svgtext = svgtext.replace('<switch>', '')
                    svgtext = svgtext.replace('</switch>', '')
                    _glyph = tempfile.NamedTemporaryFile(delete=False, suffix=".svg")
                    _glyph.file.write(svgtext)
                    _glyph.file.close()
                    yield _glyph.name

            names = tuple(os.path.splitext(os.path.basename(file_))[0] for file_, storage in files)

            codepoints = []
            for i, glyph_filename in enumerate(glyphs()):
                codepoint = i + GLYPH_START
                codepoints.append(codepoint)
                glyph = font.createChar(codepoint, names[i])
                glyph.importOutlines(glyph_filename)
                os.unlink(glyph_filename)
                if autowidth:
                    glyph.left_side_bearing = glyph.right_side_bearing = 0
                    glyph.round()
                else:
                    glyph.width = 512

            filetime = int(now_time)

            # Generate font files
            if not inline:
                urls = {}
                for type_ in ('ttf', 'svg', 'woff', 'eot'):
                    asset_path = asset_paths[type_]
                    try:
                        font.generate(asset_path)
                        # svgtext.replace('<svg>', '<svg xmlns="http://www.w3.org/2000/svg">')
                        asset_file = asset_files[type_]
                        url = '%s%s' % (config.ASSETS_URL, asset_file)
                        params = []
                        if type_ == 'eot':
                            params.append('#iefix')
                        if cache_buster:
                            params.append('v=%s' % filetime)
                        if type_ == 'svg':
                            params.append('#' + glyph_name)
                        if params:
                            url += '?' + '&'.join(params)
                        urls[type_] = url
                    except IOError:
                        inline = False
            if inline:
                urls = {}
                for type_ in ('ttf', 'svg', 'woff', 'eot'):
                    _tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.' + type_)
                    _tmp.file.close()
                    font.generate(_tmp.name)
                    with open(_tmp.name) as fh:
                        contents = fh.read()
                    os.unlink(_tmp.name)
                    # contents.replace('<svg>', '<svg xmlns="http://www.w3.org/2000/svg">')
                    mime_type = FONT_MIME_TYPES[type_]
                    url = make_data_url(mime_type, contents)
                    urls[type_] = url

            assets = {}
            for type_, url in urls.items():
                format_ = FONT_FORMATS[type_]
                url = "url('%s')" % escape(url)
                if inline:
                    assets[type_] = inline_assets[type_] = List([String.unquoted(url), String.unquoted(format_)])
                else:
                    assets[type_] = file_assets[type_] = List([String.unquoted(url), String.unquoted(format_)])
            asset = List([a for a in assets.values()], separator=",")

            # Add the new object:
            font_sheet = dict(zip(names, zip(rfiles, codepoints)))
            font_sheet['*'] = now_time
            font_sheet['*f*'] = asset_files
            font_sheet['*k*'] = key
            font_sheet['*n*'] = glyph_name
            font_sheet['*t*'] = filetime

            codepoints = zip(files, codepoints)
            cache_tmp = tempfile.NamedTemporaryFile(delete=False, dir=ASSETS_ROOT)
            pickle.dump((now_time, file_assets, inline_assets, font_sheet, codepoints), cache_tmp)
            cache_tmp.close()
            os.rename(cache_tmp.name, cache_path)

            # Use the sorted list to remove older elements (keep only 500 objects):
            if len(font_sheets) > MAX_FONT_SHEETS:
                for a in sorted(font_sheets, key=lambda a: font_sheets[a]['*'], reverse=True)[KEEP_FONT_SHEETS:]:
                    del font_sheets[a]
                log.warning("Exceeded maximum number of font sheets (%s)" % MAX_FONT_SHEETS)
            font_sheets[asset.render()] = font_sheet
        for file_, codepoint in codepoints:
            _font_sheet_cache[file_] = codepoint
    # TODO this sometimes returns an empty list, or is never assigned to
    return asset
Exemplo n.º 30
0
                        url = add_cache_buster(url, filetime)
                if inline:
                    output = six.BytesIO()
                    new_image.save(output, format='PNG')
                    contents = output.getvalue()
                    output.close()
                    if not mime_type:
                        mime_type = 'image/%s' % _path.split('.')[-1]
                    url = 'data:' + mime_type + ';base64,' + base64.b64encode(contents)
    else:
        url = os.path.join(BASE_URL.rstrip('/'), filepath.lstrip('/'))
        if cache_buster and filetime != 'NA':
            url = add_cache_buster(url, filetime)

    if not only_path:
        url = 'url(%s)' % escape(url)
    return String.unquoted(url)


@register('inline-image', 1)
@register('inline-image', 2)
@register('inline-image', 3)
@register('inline-image', 4)
@register('inline-image', 5)
def inline_image(image, mime_type=None, dst_color=None, src_color=None, spacing=None, collapse_x=None, collapse_y=None):
    """
    Embeds the contents of a file directly inside your stylesheet, eliminating
    the need for another HTTP request. For small files such images or fonts,
    this can be a performance benefit at the cost of a larger generated CSS
    file.
    """
Exemplo n.º 31
0
def font_sheet(g, **kwargs):
    if not fontforge:
        raise Exception("Fonts manipulation require fontforge")

    now_time = time.time()

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

    _k_ = ','.join(globs)

    files = None
    rfiles = None
    tfiles = None
    base_name = None
    glob_path = None
    glyph_name = None

    if _k_ in font_sheets:
        font_sheets[_k_]['*'] = now_time
    else:
        files = []
        rfiles = []
        tfiles = []
        for _glob in globs:
            if '..' not in _glob:  # Protect against going to prohibited places...
                if callable(config.STATIC_ROOT):
                    _glob_path = _glob
                    _rfiles = _files = sorted(config.STATIC_ROOT(_glob))
                else:
                    _glob_path = os.path.join(config.STATIC_ROOT, _glob)
                    _files = glob.glob(_glob_path)
                    _files = sorted((f, None) for f in _files)
                    _rfiles = [(rf[len(config.STATIC_ROOT):], s)
                               for rf, s in _files]
                if _files:
                    files.extend(_files)
                    rfiles.extend(_rfiles)
                    base_name = os.path.basename(os.path.dirname(_glob))
                    _glyph_name, _, _glyph_type = base_name.partition('.')
                    if _glyph_type:
                        _glyph_type += '-'
                    if not glyph_name:
                        glyph_name = _glyph_name
                    tfiles.extend([_glyph_type] * len(_files))
                else:
                    glob_path = _glob_path

    if files is not None:
        if not files:
            log.error("Nothing found at '%s'", glob_path)
            return String.unquoted('')

        key = [f for (f, s) in files] + [repr(kwargs), config.ASSETS_URL]
        key = glyph_name + '-' + make_filename_hash(key)
        asset_files = {
            'eot': key + '.eot',
            'woff': key + '.woff',
            'ttf': key + '.ttf',
            'svg': key + '.svg',
        }
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(
            config.STATIC_ROOT, 'assets')
        asset_paths = dict((type_, os.path.join(ASSETS_ROOT, asset_file))
                           for type_, asset_file in asset_files.items())
        cache_path = os.path.join(config.CACHE_ROOT or ASSETS_ROOT,
                                  key + '.cache')

        inline = Boolean(kwargs.get('inline', False))

        font_sheet = None
        asset = None
        file_assets = {}
        inline_assets = {}
        if all(
                os.path.exists(asset_path)
                for asset_path in asset_paths.values()) or inline:
            try:
                save_time, file_assets, inline_assets, font_sheet, codepoints = pickle.load(
                    open(cache_path))
                if file_assets:
                    file_asset = List(
                        [file_asset for file_asset in file_assets.values()],
                        separator=",")
                    font_sheets[file_asset.render()] = font_sheet
                if inline_assets:
                    inline_asset = List([
                        inline_asset
                        for inline_asset in inline_assets.values()
                    ],
                                        separator=",")
                    font_sheets[inline_asset.render()] = font_sheet
                if inline:
                    asset = inline_asset
                else:
                    asset = file_asset
            except:
                pass

            if font_sheet:
                for file_, storage in files:
                    _time = getmtime(file_, storage)
                    if save_time < _time:
                        if _time > now_time:
                            log.warning(
                                "File '%s' has a date in the future (cache ignored)"
                                % file_)
                        font_sheet = None  # Invalidate cached custom font
                        break

        if font_sheet is None or asset is None:
            cache_buster = Boolean(kwargs.get('cache_buster', True))
            autowidth = Boolean(kwargs.get('autowidth', False))
            autohint = Boolean(kwargs.get('autohint', True))

            font = fontforge.font()
            font.encoding = 'UnicodeFull'
            font.design_size = 16
            font.em = GLYPH_HEIGHT
            font.ascent = GLYPH_ASCENT
            font.descent = GLYPH_DESCENT
            font.fontname = glyph_name
            font.familyname = glyph_name
            font.fullname = glyph_name

            def glyphs(f=lambda x: x):
                for file_, storage in f(files):
                    if storage is not None:
                        _file = storage.open(file_)
                    else:
                        _file = open(file_)
                    svgtext = _file.read()
                    svgtext = svgtext.replace('<switch>', '')
                    svgtext = svgtext.replace('</switch>', '')
                    svgtext = svgtext.replace(
                        '<svg>', '<svg xmlns="http://www.w3.org/2000/svg">')
                    m = GLYPH_WIDTH_RE.search(svgtext)
                    if m:
                        width = float(m.group(1))
                    else:
                        width = None
                    m = GLYPH_HEIGHT_RE.search(svgtext)
                    if m:
                        height = float(m.group(1))
                    else:
                        height = None
                    _glyph = tempfile.NamedTemporaryFile(delete=False,
                                                         suffix=".svg")
                    _glyph.file.write(svgtext)
                    _glyph.file.close()
                    yield _glyph.name, width, height

            names = tuple(
                os.path.splitext(os.path.basename(file_))[0]
                for file_, storage in files)
            tnames = tuple(tfiles[i] + n for i, n in enumerate(names))

            codepoints = []
            for i, (glyph_filename, glyph_width,
                    glyph_height) in enumerate(glyphs()):
                if glyph_height and glyph_height != GLYPH_HEIGHT:
                    warnings.warn("Glyphs should be %spx-high" % GLYPH_HEIGHT)
                codepoint = i + GLYPH_START
                name = names[i]
                codepoints.append(codepoint)
                glyph = font.createChar(codepoint, name)
                glyph.importOutlines(glyph_filename)
                os.unlink(glyph_filename)
                glyph.width = glyph_width or GLYPH_WIDTH
                if autowidth:
                    # Autowidth removes side bearings
                    glyph.left_side_bearing = glyph.right_side_bearing = 0
                glyph.round()

            filetime = int(now_time)

            # Generate font files
            if not inline:
                urls = {}
                for type_ in reversed(FONT_TYPES):
                    asset_path = asset_paths[type_]
                    try:
                        if type_ == 'eot':
                            ttf_path = asset_paths['ttf']
                            with open(ttf_path) as ttf_fh, open(
                                    asset_path, 'wb') as asset_fh:
                                contents = ttf2eot(ttf_fh.read())
                                if contents is None:
                                    continue
                                asset_fh.write(contents)
                        else:
                            font.generate(asset_path)
                            if type_ == 'ttf':
                                contents = None
                                if autohint:
                                    with open(asset_path) as asset_fh:
                                        contents = ttfautohint(asset_fh.read())
                                if contents is not None:
                                    with open(asset_path, 'wb') as asset_fh:
                                        asset_fh.write(contents)
                        asset_file = asset_files[type_]
                        url = '%s%s' % (config.ASSETS_URL, asset_file)
                        params = []
                        if not urls:
                            params.append('#iefix')
                        if cache_buster:
                            params.append('v=%s' % filetime)
                        if type_ == 'svg':
                            params.append('#' + glyph_name)
                        if params:
                            url += '?' + '&'.join(params)
                        urls[type_] = url
                    except IOError:
                        inline = False

            if inline:
                urls = {}
                for type_ in reversed(FONT_TYPES):
                    contents = None
                    if type_ == 'eot':
                        ttf_path = asset_paths['ttf']
                        with open(ttf_path) as ttf_fh:
                            contents = ttf2eot(ttf_fh.read())
                            if contents is None:
                                continue
                    else:
                        _tmp = tempfile.NamedTemporaryFile(delete=False,
                                                           suffix='.' + type_)
                        _tmp.file.close()
                        font.generate(_tmp.name)
                        with open(_tmp.name) as asset_fh:
                            if autohint:
                                if type_ == 'ttf':
                                    _contents = asset_fh.read()
                                    contents = ttfautohint(_contents)
                            if contents is None:
                                contents = _contents
                    os.unlink(_tmp.name)
                    mime_type = FONT_MIME_TYPES[type_]
                    url = make_data_url(mime_type, contents)
                    urls[type_] = url

            assets = {}
            for type_, url in urls.items():
                format_ = FONT_FORMATS[type_]
                url = "url('%s')" % escape(url)
                if inline:
                    assets[type_] = inline_assets[type_] = List(
                        [String.unquoted(url),
                         String.unquoted(format_)])
                else:
                    assets[type_] = file_assets[type_] = List(
                        [String.unquoted(url),
                         String.unquoted(format_)])
            asset = List(
                [assets[type_] for type_ in FONT_TYPES if type_ in assets],
                separator=",")

            # Add the new object:
            font_sheet = dict(zip(tnames, zip(rfiles, codepoints)))
            font_sheet['*'] = now_time
            font_sheet['*f*'] = asset_files
            font_sheet['*k*'] = key
            font_sheet['*n*'] = glyph_name
            font_sheet['*t*'] = filetime

            codepoints = zip(files, codepoints)
            cache_tmp = tempfile.NamedTemporaryFile(delete=False,
                                                    dir=ASSETS_ROOT)
            pickle.dump(
                (now_time, file_assets, inline_assets, font_sheet, codepoints),
                cache_tmp)
            cache_tmp.close()
            os.rename(cache_tmp.name, cache_path)

            # Use the sorted list to remove older elements (keep only 500 objects):
            if len(font_sheets) > MAX_FONT_SHEETS:
                for a in sorted(font_sheets,
                                key=lambda a: font_sheets[a]['*'],
                                reverse=True)[KEEP_FONT_SHEETS:]:
                    del font_sheets[a]
                log.warning("Exceeded maximum number of font sheets (%s)" %
                            MAX_FONT_SHEETS)
            font_sheets[asset.render()] = font_sheet
        for file_, codepoint in codepoints:
            _font_sheet_cache[file_] = codepoint
    # TODO this sometimes returns an empty list, or is never assigned to
    return asset
Exemplo n.º 32
0
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`.

    $position - For `horizontal` and `vertical` directions, the position of the sprite. (defaults to `0`)
    $<sprite>-position - Position of a given sprite.

    $padding, $spacing - Adds paddings to sprites (top, right, bottom, left). (defaults to `0, 0, 0, 0`)
    $<sprite>-padding, $<sprite>-spacing - Padding for a given sprite.

    $dst-color - Together with `$src-color`, forms a map of source colors to be converted to destiny colors (same index of `$src-color` changed to `$dst-color`).
    $<sprite>-dst-color - Destiny colors for a given sprite. (defaults to `$dst-color`)

    $src-color - Selects source colors to be converted to the corresponding destiny colors. (defaults to `black`)
    $<sprite>-dst-color - Source colors for a given sprite. (defaults to `$src-color`)

    $collapse - Collapses every image in the sprite map to a fixed size (`x` and `y`).
    $collapse-x  - Collapses a size for `x`.
    $collapse-y  - Collapses a size for `y`.
    """
    if not Image:
        raise SassMissingDependency('PIL', 'image manipulation')

    sprite_maps = _get_cache('sprite_maps')

    now_time = time.time()

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

    _k_ = ','.join(globs)

    files = None
    rfiles = None
    tfiles = None
    map_name = None

    if _k_ in sprite_maps:
        sprite_maps[_k_]['*'] = now_time
    else:
        files = []
        rfiles = []
        tfiles = []
        for _glob in globs:
            if '..' not in _glob:  # Protect against going to prohibited places...
                if callable(config.STATIC_ROOT):
                    _glob_path = _glob
                    _rfiles = _files = sorted(config.STATIC_ROOT(_glob))
                else:
                    _glob_path = os.path.join(config.STATIC_ROOT, _glob)
                    _files = glob.glob(_glob_path)
                    _files = sorted((f, None) for f in _files)
                    _rfiles = [(rf[len(config.STATIC_ROOT):], s)
                               for rf, s in _files]
                if _files:
                    files.extend(_files)
                    rfiles.extend(_rfiles)
                    base_name = os.path.normpath(
                        os.path.dirname(_glob)).replace('\\',
                                                        '_').replace('/', '_')
                    _map_name, _, _map_type = base_name.partition('.')
                    if _map_type:
                        _map_type += '-'
                    if not map_name:
                        map_name = _map_name
                    tfiles.extend([_map_type] * len(_files))
                else:
                    glob_path = _glob_path

    if files is not None:
        if not files:
            log.error("Nothing found at '%s'", glob_path)
            return String.unquoted('')

        key = [f for (f, s) in files] + [repr(kwargs), config.ASSETS_URL]
        key = map_name + '-' + make_filename_hash(key)
        asset_file = key + '.png'
        ASSETS_ROOT = _assets_root()
        asset_path = os.path.join(ASSETS_ROOT, asset_file)
        cache_path = os.path.join(config.CACHE_ROOT or ASSETS_ROOT,
                                  asset_file + '.cache')

        inline = Boolean(kwargs.get('inline', False))

        sprite_map = None
        asset = None
        file_asset = None
        inline_asset = None
        if os.path.exists(asset_path) or inline:
            try:
                save_time, file_asset, inline_asset, sprite_map, sizes = pickle.load(
                    open(cache_path))
                if file_asset:
                    sprite_maps[file_asset.render()] = sprite_map
                if inline_asset:
                    sprite_maps[inline_asset.render()] = sprite_map
                if inline:
                    asset = inline_asset
                else:
                    asset = file_asset
            except:
                pass

            if sprite_map:
                for file_, storage in files:
                    _time = getmtime(file_, storage)
                    if save_time < _time:
                        if _time > now_time:
                            log.warning(
                                "File '%s' has a date in the future (cache ignored)"
                                % file_)
                        sprite_map = None  # Invalidate cached sprite map
                        break

        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
            repeat = String.unquoted(kwargs.get('repeat', 'no-repeat')).value
            collapse = kwargs.get('collapse', Number(0))
            if isinstance(collapse, List):
                collapse_x = int(Number(collapse[0]).value)
                collapse_y = int(Number(collapse[-1]).value)
            else:
                collapse_x = collapse_y = int(Number(collapse).value)
            if 'collapse_x' in kwargs:
                collapse_x = int(Number(kwargs['collapse_x']).value)
            if 'collapse_y' in kwargs:
                collapse_y = int(Number(kwargs['collapse_y']).value)

            position = Number(kwargs.get('position', 0))
            if not position.is_simple_unit('%') and position.value > 1:
                position = position.value / 100.0
            else:
                position = position.value
            if position < 0:
                position = 0.0
            elif position > 1:
                position = 1.0

            padding = kwargs.get('padding', kwargs.get('spacing', Number(0)))
            padding = [int(Number(v).value) for v in List.from_maybe(padding)]
            padding = (padding * 4)[:4]

            dst_colors = kwargs.get('dst_color')
            dst_colors = [
                list(Color(v).value[:3]) for v in List.from_maybe(dst_colors)
                if v
            ]
            src_colors = kwargs.get('src_color', Color.from_name('black'))
            src_colors = [
                tuple(Color(v).value[:3]) for v in List.from_maybe(src_colors)
            ]
            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]

            def images(f=lambda x: x):
                for file_, storage in f(files):
                    if storage is not None:
                        _file = storage.open(file_)
                    else:
                        _file = file_
                    _image = Image.open(_file)
                    yield _image

            names = tuple(
                os.path.splitext(os.path.basename(file_))[0]
                for file_, storage in files)
            tnames = tuple(tfiles[i] + n for i, n in enumerate(names))

            has_dst_colors = False
            all_dst_colors = []
            all_src_colors = []
            all_positions = []
            all_paddings = []

            for name in names:
                name = name.replace('-', '_')

                _position = kwargs.get(name + '_position')
                if _position is None:
                    _position = position
                else:
                    _position = Number(_position)
                    if not _position.is_simple_unit(
                            '%') and _position.value > 1:
                        _position = _position.value / 100.0
                    else:
                        _position = _position.value
                    if _position < 0:
                        _position = 0.0
                    elif _position > 1:
                        _position = 1.0
                all_positions.append(_position)

                _padding = kwargs.get(name + '_padding',
                                      kwargs.get(name + '_spacing'))
                if _padding is None:
                    _padding = padding
                else:
                    _padding = [
                        int(Number(v).value) for v in List.from_maybe(_padding)
                    ]
                    _padding = (_padding * 4)[:4]
                all_paddings.append(_padding)

                _dst_colors = kwargs.get(name + '_dst_color')
                if _dst_colors is None:
                    _dst_colors = dst_colors
                    if dst_colors:
                        has_dst_colors = True
                else:
                    has_dst_colors = True
                    _dst_colors = [
                        list(Color(v).value[:3])
                        for v in List.from_maybe(_dst_colors) if v
                    ]
                _src_colors = kwargs.get(name + '_src_color',
                                         Color.from_name('black'))
                if _src_colors is None:
                    _src_colors = src_colors
                else:
                    _src_colors = [
                        tuple(Color(v).value[:3])
                        for v in List.from_maybe(_src_colors)
                    ]
                _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]
                all_dst_colors.append(_dst_colors)
                all_src_colors.append(_src_colors)

            sizes = tuple((collapse_x or i.size[0], collapse_y or i.size[1])
                          for i in images())

            if direction == 'horizontal':
                layout = HorizontalSpritesLayout(sizes,
                                                 all_paddings,
                                                 position=all_positions)
            elif direction == 'vertical':
                layout = VerticalSpritesLayout(sizes,
                                               all_paddings,
                                               position=all_positions)
            elif direction == 'diagonal':
                layout = DiagonalSpritesLayout(sizes, all_paddings)
            elif direction == 'smart':
                layout = PackedSpritesLayout(sizes, all_paddings)
            else:
                raise Exception("Invalid direction %r" % (direction, ))
            layout_positions = list(layout)

            new_image = Image.new(mode='RGBA',
                                  size=(layout.width, layout.height),
                                  color=(0, 0, 0, 0))

            useless_dst_color = has_dst_colors

            offsets_x = []
            offsets_y = []
            for i, image in enumerate(images()):
                x, y, width, height, cssx, cssy, cssw, cssh = layout_positions[
                    i]
                iwidth, iheight = image.size

                if has_dst_colors:
                    pixdata = image.load()
                    for _y in xrange(iheight):
                        for _x in xrange(iwidth):
                            pixel = pixdata[_x, _y]
                            a = pixel[3] if len(pixel) == 4 else 255
                            if a:
                                rgb = pixel[:3]
                                for j, dst_color in enumerate(
                                        all_dst_colors[i]):
                                    if rgb == all_src_colors[i][j]:
                                        new_color = tuple(
                                            [int(c) for c in dst_color] + [a])
                                        if pixel != new_color:
                                            pixdata[_x, _y] = new_color
                                            useless_dst_color = False
                                        break

                if iwidth != width or iheight != height:
                    cy = 0
                    while cy < iheight:
                        cx = 0
                        while cx < iwidth:
                            new_image = alpha_composite(
                                new_image, image, (x, y),
                                (cx, cy, cx + width, cy + height))
                            cx += width
                        cy += height
                else:
                    new_image.paste(image, (x, y))
                offsets_x.append(cssx)
                offsets_y.append(cssy)

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

            filetime = int(now_time)

            if not inline:
                try:
                    new_image.save(asset_path)
                    url = '%s%s' % (config.ASSETS_URL, asset_file)
                    if cache_buster:
                        url += '?_=%s' % filetime
                except IOError:
                    log.exception("Error while saving image")
                    inline = True
            if inline:
                output = six.BytesIO()
                new_image.save(output, format='PNG')
                contents = output.getvalue()
                output.close()
                mime_type = 'image/png'
                url = make_data_url(mime_type, contents)

            url = 'url(%s)' % escape(url)
            if inline:
                asset = inline_asset = List(
                    [String.unquoted(url),
                     String.unquoted(repeat)])
            else:
                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['*'] = now_time
            sprite_map['*f*'] = asset_file
            sprite_map['*k*'] = key
            sprite_map['*n*'] = map_name
            sprite_map['*t*'] = filetime

            sizes = zip(files, sizes)
            cache_tmp = tempfile.NamedTemporaryFile(delete=False,
                                                    dir=ASSETS_ROOT)
            pickle.dump(
                (now_time, file_asset, inline_asset, sprite_map, sizes),
                cache_tmp)
            cache_tmp.close()
            if sys.platform == 'win32' and os.path.isfile(cache_path):
                # on windows, cannot rename a file to a path that matches
                # an existing file, we have to remove it first
                os.remove(cache_path)
            os.rename(cache_tmp.name, cache_path)

            # Use the sorted list to remove older elements (keep only 500 objects):
            if len(sprite_maps) > MAX_SPRITE_MAPS:
                for a in sorted(sprite_maps,
                                key=lambda a: sprite_maps[a]['*'],
                                reverse=True)[KEEP_SPRITE_MAPS:]:
                    del sprite_maps[a]
                log.warning("Exceeded maximum number of sprite maps (%s)" %
                            MAX_SPRITE_MAPS)
            sprite_maps[asset.render()] = sprite_map
        image_size_cache = _get_cache('image_size_cache')
        for file_, size in sizes:
            image_size_cache[file_] = size
    # TODO this sometimes returns an empty list, or is never assigned to
    return asset
Exemplo n.º 33
0
 def __str__(self):
     if self.quotes:
         return self.quotes + escape(self.value) + self.quotes
     else:
         return self.value
Exemplo n.º 34
0
def _image_url(path,
               only_path=False,
               cache_buster=True,
               dst_color=None,
               src_color=None,
               inline=False,
               mime_type=None,
               spacing=None,
               collapse_x=None,
               collapse_y=None):
    """
    src_color - a list of or a single color to be replaced by each corresponding dst_color colors
    spacing - spaces to be added to the image
    collapse_x, collapse_y - collapsable (layered) image of the given size (x, y)
    """
    if inline or dst_color or spacing:
        if not Image:
            raise Exception("Images manipulation require PIL")
    filepath = String.unquoted(path).value
    fileext = os.path.splitext(filepath)[1].lstrip('.').lower()
    if mime_type:
        mime_type = String.unquoted(mime_type).value
    if not mime_type:
        mime_type = mimetypes.guess_type(filepath)[0]
    if not mime_type:
        mime_type = 'image/%s' % fileext
    path = None
    IMAGES_ROOT = _images_root()
    if callable(IMAGES_ROOT):
        try:
            _file, _storage = list(IMAGES_ROOT(filepath))[0]
        except IndexError:
            filetime = None
        else:
            filetime = getmtime(_file, _storage)
        if filetime is None:
            filetime = 'NA'
        elif inline or dst_color or spacing:
            path = _storage.open(_file)
    else:
        _path = os.path.join(IMAGES_ROOT.rstrip('/'), filepath.strip('/'))
        filetime = getmtime(_path)
        if filetime is None:
            filetime = 'NA'
        elif inline or dst_color or spacing:
            path = open(_path, 'rb')

    BASE_URL = config.IMAGES_URL or config.STATIC_URL
    if path:
        dst_colors = [
            list(Color(v).value[:3]) for v in List.from_maybe(dst_color) if v
        ]

        src_color = Color.from_name(
            'black') if src_color is None else src_color
        src_colors = [
            tuple(Color(v).value[:3]) for v in List.from_maybe(src_color)
        ]

        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 = Number(0) if spacing is None else spacing
        spacing = [int(Number(v).value) for v in List.from_maybe(spacing)]
        spacing = (spacing * 4)[:4]

        file_name, file_ext = os.path.splitext(
            os.path.normpath(filepath).replace('\\', '_').replace('/', '_'))
        key = (filetime, src_color, dst_color, spacing)
        key = file_name + '-' + base64.urlsafe_b64encode(
            hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_')
        asset_file = key + file_ext
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(
            config.STATIC_ROOT, 'assets')
        asset_path = os.path.join(ASSETS_ROOT, asset_file)

        if os.path.exists(asset_path):
            filepath = asset_file
            BASE_URL = config.ASSETS_URL
            if inline:
                path = open(asset_path, 'rb')
                url = 'data:' + mime_type + ';base64,' + base64.b64encode(
                    path.read())
            else:
                url = '%s%s' % (BASE_URL, filepath)
                if cache_buster:
                    filetime = getmtime(asset_path)
                    url = add_cache_buster(url, filetime)
        else:
            simply_process = False
            image = None

            if fileext in ('cur', ):
                simply_process = True
            else:
                try:
                    image = Image.open(path)
                except IOError:
                    if not collapse_x and not collapse_y and not dst_colors:
                        simply_process = True

            if simply_process:
                if inline:
                    url = 'data:' + mime_type + ';base64,' + base64.b64encode(
                        path.read())
                else:
                    url = '%s%s' % (BASE_URL, filepath)
                    if cache_buster:
                        filetime = getmtime(asset_path)
                        url = add_cache_buster(url, filetime)
            else:
                width, height = collapse_x or image.size[
                    0], collapse_y or image.size[1]
                new_image = Image.new(mode='RGBA',
                                      size=(width + spacing[1] + spacing[3],
                                            height + spacing[0] + spacing[2]),
                                      color=(0, 0, 0, 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])
                iwidth, iheight = image.size
                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(spacing[3]), int(spacing[0])),
                                            cropped_image)
                            cx += width
                        cy += height
                else:
                    new_image.paste(image, (int(spacing[3]), int(spacing[0])))

                if not inline:
                    try:
                        new_image.save(asset_path)
                        filepath = asset_file
                        BASE_URL = config.ASSETS_URL
                        if cache_buster:
                            filetime = getmtime(asset_path)
                    except IOError:
                        log.exception("Error while saving image")
                        inline = True  # Retry inline version
                    url = os.path.join(config.ASSETS_URL.rstrip('/'),
                                       asset_file.lstrip('/'))
                    if cache_buster:
                        url = add_cache_buster(url, filetime)
                if inline:
                    output = six.BytesIO()
                    new_image.save(output, format='PNG')
                    contents = output.getvalue()
                    output.close()
                    url = 'data:' + mime_type + ';base64,' + base64.b64encode(
                        contents)
    else:
        url = os.path.join(BASE_URL.rstrip('/'), filepath.lstrip('/'))
        if cache_buster and filetime != 'NA':
            url = add_cache_buster(url, filetime)

    if not only_path:
        url = 'url(%s)' % escape(url)
    return String.unquoted(url)
Exemplo n.º 35
0
def _image_url(path, only_path=False, cache_buster=True, dst_color=None, src_color=None, inline=False, mime_type=None, spacing=None, collapse_x=None, collapse_y=None):
    """
    src_color - a list of or a single color to be replaced by each corresponding dst_color colors
    spacing - spaces to be added to the image
    collapse_x, collapse_y - collapsable (layered) image of the given size (x, y)
    """
    if inline or dst_color or spacing:
        if not Image:
            raise Exception("Images manipulation require PIL")
    filepath = StringValue(path).value
    mime_type = inline and (StringValue(mime_type).value or mimetypes.guess_type(filepath)[0])
    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()))
            if inline or dst_color or spacing:
                path = _storage.open(_file)
        except:
            filetime = 'NA'
    else:
        _path = os.path.join(config.STATIC_ROOT, filepath.strip('/'))
        if os.path.exists(_path):
            filetime = int(os.path.getmtime(_path))
            if inline or dst_color or spacing:
                path = open(_path, 'rb')
        else:
            filetime = 'NA'
    BASE_URL = config.STATIC_URL
    if path:
        dst_colors = [list(ColorValue(v).value[:3]) for v in List.from_maybe(dst_color) if v]

        src_colors = src_color
        src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) for v in List.from_maybe(src_color)]

        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 = [int(NumberValue(v).value) if v else 0 for v in List.from_maybe(spacing)]
        spacing = (spacing * 4)[:4]

        file_name, file_ext = os.path.splitext(os.path.normpath(filepath).replace('\\', '_').replace('/', '_'))
        key = (filetime, src_color, dst_color, spacing)
        key = file_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_')
        asset_file = key + file_ext
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets')
        asset_path = os.path.join(ASSETS_ROOT, asset_file)

        if os.path.exists(asset_path):
            filepath = asset_file
            BASE_URL = config.ASSETS_URL
            if inline:
                path = open(asset_path, 'rb')
                url = 'data:' + mime_type + ';base64,' + base64.b64encode(path.read())
            else:
                url = '%s%s' % (BASE_URL, filepath)
                if cache_buster:
                    filetime = int(os.path.getmtime(asset_path))
                    url = add_cache_buster(url, filetime)
        else:
            image = Image.open(path)
            width, height = collapse_x or image.size[0], collapse_y or image.size[1]
            new_image = Image.new(
                mode='RGBA',
                size=(width + spacing[1] + spacing[3], height + spacing[0] + spacing[2]),
                color=(0, 0, 0, 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])
            iwidth, iheight = image.size
            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(spacing[3]), int(spacing[0])), cropped_image)
                        cx += width
                    cy += height
            else:
                new_image.paste(image, (int(spacing[3]), int(spacing[0])))

            if not inline:
                try:
                    new_image.save(asset_path)
                    filepath = asset_file
                    BASE_URL = config.ASSETS_URL
                    if cache_buster:
                        filetime = int(os.path.getmtime(asset_path))
                except IOError:
                    log.exception("Error while saving image")
                    inline = True  # Retry inline version
                url = '%s%s' % (config.ASSETS_URL, asset_file)
                if cache_buster:
                    url = add_cache_buster(url, filetime)
            if inline:
                output = six.BytesIO()
                new_image.save(output, format='PNG')
                contents = output.getvalue()
                output.close()
                url = 'data:' + mime_type + ';base64,' + base64.b64encode(contents)
    else:
        url = '%s%s' % (BASE_URL, filepath)
        if cache_buster:
            url = add_cache_buster(url, filetime)

    if not only_path:
        url = 'url("%s")' % escape(url)
    return StringValue(url)
Exemplo n.º 36
0
def grid_image(left_gutter, width, right_gutter, height, columns=1, grid_color=None, baseline_color=None, background_color=None, inline=False):
    if not Image:
        raise SassMissingDependency('PIL', 'image manipulation')
    if grid_color is None:
        grid_color = (120, 170, 250, 15)
    else:
        c = Color(grid_color).value
        grid_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    if baseline_color is None:
        baseline_color = (120, 170, 250, 30)
    else:
        c = Color(baseline_color).value
        baseline_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    if background_color is None:
        background_color = (0, 0, 0, 0)
    else:
        c = Color(background_color).value
        background_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    _height = int(height) if height >= 1 else int(height * 1000.0)
    _width = int(width) if width >= 1 else int(width * 1000.0)
    _left_gutter = int(left_gutter) if left_gutter >= 1 else int(left_gutter * 1000.0)
    _right_gutter = int(right_gutter) if right_gutter >= 1 else int(right_gutter * 1000.0)
    if _height <= 0 or _width <= 0 or _left_gutter <= 0 or _right_gutter <= 0:
        raise ValueError
    _full_width = (_left_gutter + _width + _right_gutter)
    new_image = Image.new(
        mode='RGBA',
        size=(_full_width * int(columns), _height),
        color=background_color
    )
    draw = ImageDraw.Draw(new_image)
    for i in range(int(columns)):
        draw.rectangle((i * _full_width + _left_gutter, 0, i * _full_width + _left_gutter + _width - 1, _height - 1),  fill=grid_color)
    if _height > 1:
        draw.rectangle((0, _height - 1, _full_width * int(columns) - 1, _height - 1),  fill=baseline_color)
    if not inline:
        grid_name = 'grid_'
        if left_gutter:
            grid_name += str(int(left_gutter)) + '+'
        grid_name += str(int(width))
        if right_gutter:
            grid_name += '+' + str(int(right_gutter))
        if height and height > 1:
            grid_name += 'x' + str(int(height))
        key = (columns, grid_color, baseline_color, background_color)
        key = grid_name + '-' + make_filename_hash(key)
        asset_file = key + '.png'
        asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = six.BytesIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = make_data_url('image/png', contents)
    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Exemplo n.º 37
0
 def __str__(self):
     if self.quotes:
         return self.quotes + escape(self.value) + self.quotes
     else:
         return self.value
Exemplo n.º 38
0
def font_sheet(g, **kwargs):
    if not fontforge:
        raise Exception("Fonts manipulation require fontforge")

    now_time = time.time()

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

    _k_ = ','.join(globs)

    files = None
    rfiles = None
    tfiles = None
    base_name = None
    glob_path = None
    glyph_name = None

    if _k_ in font_sheets:
        font_sheets[_k_]['*'] = now_time
    else:
        files = []
        rfiles = []
        tfiles = []
        for _glob in globs:
            if '..' not in _glob:  # Protect against going to prohibited places...
                if callable(config.STATIC_ROOT):
                    _glob_path = _glob
                    _rfiles = _files = sorted(config.STATIC_ROOT(_glob))
                else:
                    _glob_path = os.path.join(config.STATIC_ROOT, _glob)
                    _files = glob.glob(_glob_path)
                    _files = sorted((f, None) for f in _files)
                    _rfiles = [(rf[len(config.STATIC_ROOT):], s) for rf, s in _files]
                if _files:
                    files.extend(_files)
                    rfiles.extend(_rfiles)
                    base_name = os.path.basename(os.path.dirname(_glob))
                    _glyph_name, _, _glyph_type = base_name.partition('.')
                    if _glyph_type:
                        _glyph_type += '-'
                    if not glyph_name:
                        glyph_name = _glyph_name
                    tfiles.extend([_glyph_type] * len(_files))
                else:
                    glob_path = _glob_path

    if files is not None:
        if not files:
            log.error("Nothing found at '%s'", glob_path)
            return String.unquoted('')

        key = [f for (f, s) in files] + [repr(kwargs), config.ASSETS_URL]
        key = glyph_name + '-' + make_filename_hash(key)
        asset_files = {
            'eot': key + '.eot',
            'woff': key + '.woff',
            'ttf': key + '.ttf',
            'svg': key + '.svg',
        }
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets')
        asset_paths = dict((type_, os.path.join(ASSETS_ROOT, asset_file)) for type_, asset_file in asset_files.items())
        cache_path = os.path.join(config.CACHE_ROOT or ASSETS_ROOT, key + '.cache')

        inline = Boolean(kwargs.get('inline', False))

        font_sheet = None
        asset = None
        file_assets = {}
        inline_assets = {}
        if all(os.path.exists(asset_path) for asset_path in asset_paths.values()) or inline:
            try:
                save_time, file_assets, inline_assets, font_sheet, codepoints = pickle.load(open(cache_path))
                if file_assets:
                    file_asset = List([file_asset for file_asset in file_assets.values()], separator=",")
                    font_sheets[file_asset.render()] = font_sheet
                if inline_assets:
                    inline_asset = List([inline_asset for inline_asset in inline_assets.values()], separator=",")
                    font_sheets[inline_asset.render()] = font_sheet
                if inline:
                    asset = inline_asset
                else:
                    asset = file_asset
            except:
                pass

            if font_sheet:
                for file_, storage in files:
                    _time = getmtime(file_, storage)
                    if save_time < _time:
                        if _time > now_time:
                            log.warning("File '%s' has a date in the future (cache ignored)" % file_)
                        font_sheet = None  # Invalidate cached custom font
                        break

        if font_sheet is None or asset is None:
            cache_buster = Boolean(kwargs.get('cache_buster', True))
            autowidth = Boolean(kwargs.get('autowidth', False))
            autohint = Boolean(kwargs.get('autohint', True))

            font = fontforge.font()
            font.encoding = 'UnicodeFull'
            font.design_size = 16
            font.em = GLYPH_HEIGHT
            font.ascent = GLYPH_ASCENT
            font.descent = GLYPH_DESCENT
            font.fontname = glyph_name
            font.familyname = glyph_name
            font.fullname = glyph_name

            def glyphs(f=lambda x: x):
                for file_, storage in f(files):
                    if storage is not None:
                        _file = storage.open(file_)
                    else:
                        _file = open(file_)
                    svgtext = _file.read()
                    svgtext = svgtext.replace('<switch>', '')
                    svgtext = svgtext.replace('</switch>', '')
                    svgtext = svgtext.replace('<svg>', '<svg xmlns="http://www.w3.org/2000/svg">')
                    m = GLYPH_WIDTH_RE.search(svgtext)
                    if m:
                        width = float(m.group(1))
                    else:
                        width = None
                    m = GLYPH_HEIGHT_RE.search(svgtext)
                    if m:
                        height = float(m.group(1))
                    else:
                        height = None
                    _glyph = tempfile.NamedTemporaryFile(delete=False, suffix=".svg")
                    _glyph.file.write(svgtext)
                    _glyph.file.close()
                    yield _glyph.name, width, height

            names = tuple(os.path.splitext(os.path.basename(file_))[0] for file_, storage in files)
            tnames = tuple(tfiles[i] + n for i, n in enumerate(names))

            codepoints = []
            for i, (glyph_filename, glyph_width, glyph_height) in enumerate(glyphs()):
                if glyph_height and glyph_height != GLYPH_HEIGHT:
                    warnings.warn("Glyphs should be %spx-high" % GLYPH_HEIGHT)
                codepoint = i + GLYPH_START
                name = names[i]
                codepoints.append(codepoint)
                glyph = font.createChar(codepoint, name)
                glyph.importOutlines(glyph_filename)
                os.unlink(glyph_filename)
                glyph.width = glyph_width or GLYPH_WIDTH
                if autowidth:
                    # Autowidth removes side bearings
                    glyph.left_side_bearing = glyph.right_side_bearing = 0
                glyph.round()

            filetime = int(now_time)

            # Generate font files
            if not inline:
                urls = {}
                for type_ in reversed(FONT_TYPES):
                    asset_path = asset_paths[type_]
                    try:
                        if type_ == 'eot':
                            ttf_path = asset_paths['ttf']
                            with open(ttf_path) as ttf_fh:
                                contents = ttf2eot(ttf_fh.read())
                                if contents is not None:
                                    with open(asset_path, 'wb') as asset_fh:
                                        asset_fh.write(contents)
                        else:
                            font.generate(asset_path)
                            if type_ == 'ttf':
                                contents = None
                                if autohint:
                                    with open(asset_path) as asset_fh:
                                        contents = ttfautohint(asset_fh.read())
                                if contents is not None:
                                    with open(asset_path, 'wb') as asset_fh:
                                        asset_fh.write(contents)
                        asset_file = asset_files[type_]
                        url = '%s%s' % (config.ASSETS_URL, asset_file)
                        params = []
                        if not urls:
                            params.append('#iefix')
                        if cache_buster:
                            params.append('v=%s' % filetime)
                        if type_ == 'svg':
                            params.append('#' + glyph_name)
                        if params:
                            url += '?' + '&'.join(params)
                        urls[type_] = url
                    except IOError:
                        inline = False

            if inline:
                urls = {}
                for type_ in reversed(FONT_TYPES):
                    contents = None
                    if type_ == 'eot':
                        ttf_path = asset_paths['ttf']
                        with open(ttf_path) as ttf_fh:
                            contents = ttf2eot(ttf_fh.read())
                            if contents is None:
                                continue
                    else:
                        _tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.' + type_)
                        _tmp.file.close()
                        font.generate(_tmp.name)
                        with open(_tmp.name) as asset_fh:
                            if autohint:
                                if type_ == 'ttf':
                                    _contents = asset_fh.read()
                                    contents = ttfautohint(_contents)
                            if contents is None:
                                contents = _contents
                    os.unlink(_tmp.name)
                    mime_type = FONT_MIME_TYPES[type_]
                    url = make_data_url(mime_type, contents)
                    urls[type_] = url

            assets = {}
            for type_, url in urls.items():
                format_ = FONT_FORMATS[type_]
                url = "url('%s')" % escape(url)
                if inline:
                    assets[type_] = inline_assets[type_] = List([String.unquoted(url), String.unquoted(format_)])
                else:
                    assets[type_] = file_assets[type_] = List([String.unquoted(url), String.unquoted(format_)])
            asset = List([assets[type_] for type_ in FONT_TYPES if type_ in assets], separator=",")

            # Add the new object:
            font_sheet = dict(zip(tnames, zip(rfiles, codepoints)))
            font_sheet['*'] = now_time
            font_sheet['*f*'] = asset_files
            font_sheet['*k*'] = key
            font_sheet['*n*'] = glyph_name
            font_sheet['*t*'] = filetime

            codepoints = zip(files, codepoints)
            cache_tmp = tempfile.NamedTemporaryFile(delete=False, dir=ASSETS_ROOT)
            pickle.dump((now_time, file_assets, inline_assets, font_sheet, codepoints), cache_tmp)
            cache_tmp.close()
            os.rename(cache_tmp.name, cache_path)

            # Use the sorted list to remove older elements (keep only 500 objects):
            if len(font_sheets) > MAX_FONT_SHEETS:
                for a in sorted(font_sheets, key=lambda a: font_sheets[a]['*'], reverse=True)[KEEP_FONT_SHEETS:]:
                    del font_sheets[a]
                log.warning("Exceeded maximum number of font sheets (%s)" % MAX_FONT_SHEETS)
            font_sheets[asset.render()] = font_sheet
        for file_, codepoint in codepoints:
            _font_sheet_cache[file_] = codepoint
    # TODO this sometimes returns an empty list, or is never assigned to
    return asset
Exemplo n.º 39
0
def _grid_image(left_gutter, width, right_gutter, height, columns=1, grid_color=None, baseline_color=None, background_color=None, inline=False):
    if not Image:
        raise Exception("Images manipulation require PIL")
    if grid_color is None:
        grid_color = (120, 170, 250, 15)
    else:
        c = ColorValue(grid_color).value
        grid_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    if baseline_color is None:
        baseline_color = (120, 170, 250, 30)
    else:
        c = ColorValue(baseline_color).value
        baseline_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    if background_color is None:
        background_color = (0, 0, 0, 0)
    else:
        c = ColorValue(background_color).value
        background_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    _height = int(height) if height >= 1 else int(height * 1000.0)
    _width = int(width) if width >= 1 else int(width * 1000.0)
    _left_gutter = int(left_gutter) if left_gutter >= 1 else int(left_gutter * 1000.0)
    _right_gutter = int(right_gutter) if right_gutter >= 1 else int(right_gutter * 1000.0)
    if _height <= 0 or _width <= 0 or _left_gutter <= 0 or _right_gutter <= 0:
        raise ValueError
    _full_width = (_left_gutter + _width + _right_gutter)
    new_image = Image.new(
        mode='RGBA',
        size=(_full_width * int(columns), _height),
        color=background_color
    )
    draw = ImageDraw.Draw(new_image)
    for i in range(int(columns)):
        draw.rectangle((i * _full_width + _left_gutter, 0, i * _full_width + _left_gutter + _width - 1, _height - 1),  fill=grid_color)
    if _height > 1:
        draw.rectangle((0, _height - 1, _full_width * int(columns) - 1, _height - 1),  fill=baseline_color)
    if not inline:
        grid_name = 'grid_'
        if left_gutter:
            grid_name += str(int(left_gutter)) + '+'
        grid_name += str(int(width))
        if right_gutter:
            grid_name += '+' + str(int(right_gutter))
        if height and height > 1:
            grid_name += 'x' + str(int(height))
        key = (columns, grid_color, baseline_color, background_color)
        key = grid_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_')
        asset_file = key + '.png'
        asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = StringIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = 'data:image/png;base64,' + base64.b64encode(contents)
    inline = 'url("%s")' % escape(url)
    return StringValue(inline)
Exemplo n.º 40
0
def _image_url(
    path,
    only_path=False,
    cache_buster=True,
    dst_color=None,
    src_color=None,
    inline=False,
    mime_type=None,
    spacing=None,
    collapse_x=None,
    collapse_y=None,
):
    """
    src_color - a list of or a single color to be replaced by each corresponding dst_color colors
    spacing - spaces to be added to the image
    collapse_x, collapse_y - collapsable (layered) image of the given size (x, y)
    """
    if inline or dst_color or spacing:
        if not Image:
            raise Exception("Images manipulation require PIL")
    filepath = String.unquoted(path).value
    fileext = os.path.splitext(filepath)[1].lstrip(".").lower()
    if mime_type:
        mime_type = String.unquoted(mime_type).value
    if not mime_type:
        mime_type = mimetypes.guess_type(filepath)[0]
    if not mime_type:
        mime_type = "image/%s" % fileext
    path = None
    IMAGES_ROOT = _images_root()
    if callable(IMAGES_ROOT):
        try:
            _file, _storage = list(IMAGES_ROOT(filepath))[0]
            d_obj = _storage.modified_time(_file)
            filetime = int(time.mktime(d_obj.timetuple()))
            if inline or dst_color or spacing:
                path = _storage.open(_file)
        except:
            filetime = "NA"
    else:
        _path = os.path.join(IMAGES_ROOT.rstrip("/"), filepath.strip("/"))
        if os.path.exists(_path):
            filetime = int(os.path.getmtime(_path))
            if inline or dst_color or spacing:
                path = open(_path, "rb")
        else:
            filetime = "NA"

    BASE_URL = config.IMAGES_URL or config.STATIC_URL
    if path:
        dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(dst_color) if v]

        src_color = Color.from_name("black") if src_color is None else src_color
        src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(src_color)]

        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 = Number(0) if spacing is None else spacing
        spacing = [int(Number(v).value) for v in List.from_maybe(spacing)]
        spacing = (spacing * 4)[:4]

        file_name, file_ext = os.path.splitext(os.path.normpath(filepath).replace("\\", "_").replace("/", "_"))
        key = (filetime, src_color, dst_color, spacing)
        key = file_name + "-" + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip("=").replace("-", "_")
        asset_file = key + file_ext
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, "assets")
        asset_path = os.path.join(ASSETS_ROOT, asset_file)

        if os.path.exists(asset_path):
            filepath = asset_file
            BASE_URL = config.ASSETS_URL
            if inline:
                path = open(asset_path, "rb")
                url = "data:" + mime_type + ";base64," + base64.b64encode(path.read())
            else:
                url = "%s%s" % (BASE_URL, filepath)
                if cache_buster:
                    filetime = int(os.path.getmtime(asset_path))
                    url = add_cache_buster(url, filetime)
        else:
            simply_process = False
            image = None

            if fileext in ("cur",):
                simply_process = True
            else:
                try:
                    image = Image.open(path)
                except IOError:
                    if not collapse_x and not collapse_y and not dst_colors:
                        simply_process = True

            if simply_process:
                if inline:
                    url = "data:" + mime_type + ";base64," + base64.b64encode(path.read())
                else:
                    url = "%s%s" % (BASE_URL, filepath)
                    if cache_buster:
                        filetime = int(os.path.getmtime(asset_path))
                        url = add_cache_buster(url, filetime)
            else:
                width, height = collapse_x or image.size[0], collapse_y or image.size[1]
                new_image = Image.new(
                    mode="RGBA",
                    size=(width + spacing[1] + spacing[3], height + spacing[0] + spacing[2]),
                    color=(0, 0, 0, 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]
                                )
                iwidth, iheight = image.size
                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(spacing[3]), int(spacing[0])), cropped_image)
                            cx += width
                        cy += height
                else:
                    new_image.paste(image, (int(spacing[3]), int(spacing[0])))

                if not inline:
                    try:
                        new_image.save(asset_path)
                        filepath = asset_file
                        BASE_URL = config.ASSETS_URL
                        if cache_buster:
                            filetime = int(os.path.getmtime(asset_path))
                    except IOError:
                        log.exception("Error while saving image")
                        inline = True  # Retry inline version
                    url = os.path.join(config.ASSETS_URL.rstrip("/"), asset_file.lstrip("/"))
                    if cache_buster:
                        url = add_cache_buster(url, filetime)
                if inline:
                    output = six.BytesIO()
                    new_image.save(output, format="PNG")
                    contents = output.getvalue()
                    output.close()
                    url = "data:" + mime_type + ";base64," + base64.b64encode(contents)
    else:
        url = os.path.join(BASE_URL.rstrip("/"), filepath.lstrip("/"))
        if cache_buster and filetime != "NA":
            url = add_cache_buster(url, filetime)

    if not only_path:
        url = "url(%s)" % escape(url)
    return String.unquoted(url)
Exemplo n.º 41
0
def _grid_image(left_gutter, width, right_gutter, height, columns=1, grid_color=None, baseline_color=None, background_color=None, inline=False):
    if not Image:
        raise Exception("Images manipulation require PIL")
    if grid_color is None:
        grid_color = (120, 170, 250, 15)
    else:
        c = Color(grid_color).value
        grid_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    if baseline_color is None:
        baseline_color = (120, 170, 250, 30)
    else:
        c = Color(baseline_color).value
        baseline_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    if background_color is None:
        background_color = (0, 0, 0, 0)
    else:
        c = Color(background_color).value
        background_color = (c[0], c[1], c[2], int(c[3] * 255.0))
    _height = int(height) if height >= 1 else int(height * 1000.0)
    _width = int(width) if width >= 1 else int(width * 1000.0)
    _left_gutter = int(left_gutter) if left_gutter >= 1 else int(left_gutter * 1000.0)
    _right_gutter = int(right_gutter) if right_gutter >= 1 else int(right_gutter * 1000.0)
    if _height <= 0 or _width <= 0 or _left_gutter <= 0 or _right_gutter <= 0:
        raise ValueError
    _full_width = (_left_gutter + _width + _right_gutter)
    new_image = Image.new(
        mode='RGBA',
        size=(_full_width * int(columns), _height),
        color=background_color
    )
    draw = ImageDraw.Draw(new_image)
    for i in range(int(columns)):
        draw.rectangle((i * _full_width + _left_gutter, 0, i * _full_width + _left_gutter + _width - 1, _height - 1),  fill=grid_color)
    if _height > 1:
        draw.rectangle((0, _height - 1, _full_width * int(columns) - 1, _height - 1),  fill=baseline_color)
    if not inline:
        grid_name = 'grid_'
        if left_gutter:
            grid_name += str(int(left_gutter)) + '+'
        grid_name += str(int(width))
        if right_gutter:
            grid_name += '+' + str(int(right_gutter))
        if height and height > 1:
            grid_name += 'x' + str(int(height))
        key = (columns, grid_color, baseline_color, background_color)
        key = grid_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_')
        asset_file = key + '.png'
        asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = six.BytesIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = 'data:image/png;base64,' + base64.b64encode(contents)
    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Exemplo n.º 42
0
def font_sheet(g, **kwargs):
    if not fontforge:
        raise Exception("Fonts manipulation require fontforge")

    now_time = time.time()

    g = String(g, quotes=None).value

    if g in font_sheets:
        font_sheets[glob]['*'] = now_time
    elif '..' not in g:  # Protect against going to prohibited places...
        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 = [(rf[len(config.STATIC_ROOT):], s) for rf, s in files]

        if not files:
            log.error("Nothing found at '%s'", glob_path)
            return String.unquoted('')

        glyph_name = os.path.normpath(os.path.dirname(g)).replace('\\',
                                                                  '_').replace(
                                                                      '/', '_')
        key = [f for (f, s) in files] + [repr(kwargs), config.ASSETS_URL]
        key = glyph_name + '-' + make_filename_hash(key)
        asset_files = {
            'ttf': key + '.ttf',
            'svg': key + '.svg',
            'woff': key + '.woff',
            'eot': key + '.eot',
        }
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(
            config.STATIC_ROOT, 'assets')
        asset_paths = dict((type_, os.path.join(ASSETS_ROOT, asset_file))
                           for type_, asset_file in asset_files.items())
        cache_path = os.path.join(config.CACHE_ROOT or ASSETS_ROOT,
                                  key + '.cache')

        inline = Boolean(kwargs.get('inline', False))

        font_sheet = None
        asset = None
        file_assets = {}
        inline_assets = {}
        if all(
                os.path.exists(asset_path)
                for asset_path in asset_paths.values()) or inline:
            try:
                save_time, file_assets, inline_assets, font_sheet, codepoints = pickle.load(
                    open(cache_path))
                if file_assets:
                    file_asset = List(
                        [file_asset for file_asset in file_assets.values()],
                        separator=",")
                    font_sheets[file_asset.render()] = font_sheet
                if inline_assets:
                    inline_asset = List([
                        inline_asset
                        for inline_asset in inline_assets.values()
                    ],
                                        separator=",")
                    font_sheets[inline_asset.render()] = font_sheet
                if inline:
                    asset = inline_asset
                else:
                    asset = file_asset
            except:
                pass

            if font_sheet:
                for file_, storage in files:
                    _time = getmtime(file_, storage)
                    if save_time < _time:
                        if _time > now_time:
                            log.warning(
                                "File '%s' has a date in the future (cache ignored)"
                                % file_)
                        font_sheet = None  # Invalidate cached custom font
                        break

        if font_sheet is None or asset is None:
            cache_buster = Boolean(kwargs.get('cache_buster', True))
            autowidth = Boolean(kwargs.get('autowidth', True))
            autohint = Boolean(kwargs.get('autohint', True))

            font = fontforge.font()
            font.encoding = 'UnicodeFull'
            font.design_size = 16
            font.em = 512
            font.ascent = 448
            font.descent = 64
            font.fontname = glyph_name
            font.familyname = glyph_name
            font.fullname = glyph_name
            if autowidth:
                font.autoWidth(0, 0, 512)
            if autohint:
                font.autoHint()

            def glyphs(f=lambda x: x):
                for file_, storage in f(files):
                    if storage is not None:
                        _file = storage.open(file_)
                    else:
                        _file = open(file_)
                    svgtext = _file.read()
                    svgtext = svgtext.replace('<switch>', '')
                    svgtext = svgtext.replace('</switch>', '')
                    _glyph = tempfile.NamedTemporaryFile(delete=False,
                                                         suffix=".svg")
                    _glyph.file.write(svgtext)
                    _glyph.file.close()
                    yield _glyph.name

            names = tuple(
                os.path.splitext(os.path.basename(file_))[0]
                for file_, storage in files)

            codepoints = []
            for i, glyph_filename in enumerate(glyphs()):
                codepoint = i + GLYPH_START
                codepoints.append(codepoint)
                glyph = font.createChar(codepoint, names[i])
                glyph.importOutlines(glyph_filename)
                os.unlink(glyph_filename)
                if autowidth:
                    glyph.left_side_bearing = glyph.right_side_bearing = 0
                    glyph.round()
                else:
                    glyph.width = 512

            filetime = int(now_time)

            # Generate font files
            if not inline:
                urls = {}
                for type_ in ('ttf', 'svg', 'woff', 'eot'):
                    asset_path = asset_paths[type_]
                    try:
                        font.generate(asset_path)
                        # svgtext.replace('<svg>', '<svg xmlns="http://www.w3.org/2000/svg">')
                        asset_file = asset_files[type_]
                        url = '%s%s' % (config.ASSETS_URL, asset_file)
                        params = []
                        if type_ == 'eot':
                            params.append('#iefix')
                        if cache_buster:
                            params.append('v=%s' % filetime)
                        if type_ == 'svg':
                            params.append('#' + glyph_name)
                        if params:
                            url += '?' + '&'.join(params)
                        urls[type_] = url
                    except IOError:
                        inline = False
            if inline:
                urls = {}
                for type_ in ('ttf', 'svg', 'woff', 'eot'):
                    _tmp = tempfile.NamedTemporaryFile(delete=False,
                                                       suffix='.' + type_)
                    _tmp.file.close()
                    font.generate(_tmp.name)
                    with open(_tmp.name) as fh:
                        contents = fh.read()
                    os.unlink(_tmp.name)
                    # contents.replace('<svg>', '<svg xmlns="http://www.w3.org/2000/svg">')
                    mime_type = FONT_MIME_TYPES[type_]
                    url = make_data_url(mime_type, contents)
                    urls[type_] = url

            assets = {}
            for type_, url in urls.items():
                format_ = FONT_FORMATS[type_]
                url = "url('%s')" % escape(url)
                if inline:
                    assets[type_] = inline_assets[type_] = List(
                        [String.unquoted(url),
                         String.unquoted(format_)])
                else:
                    assets[type_] = file_assets[type_] = List(
                        [String.unquoted(url),
                         String.unquoted(format_)])
            asset = List([a for a in assets.values()], separator=",")

            # Add the new object:
            font_sheet = dict(zip(names, zip(rfiles, codepoints)))
            font_sheet['*'] = now_time
            font_sheet['*f*'] = asset_files
            font_sheet['*k*'] = key
            font_sheet['*n*'] = glyph_name
            font_sheet['*t*'] = filetime

            codepoints = zip(files, codepoints)
            cache_tmp = tempfile.NamedTemporaryFile(delete=False,
                                                    dir=ASSETS_ROOT)
            pickle.dump(
                (now_time, file_assets, inline_assets, font_sheet, codepoints),
                cache_tmp)
            cache_tmp.close()
            os.rename(cache_tmp.name, cache_path)

            # Use the sorted list to remove older elements (keep only 500 objects):
            if len(font_sheets) > MAX_FONT_SHEETS:
                for a in sorted(font_sheets,
                                key=lambda a: font_sheets[a]['*'],
                                reverse=True)[KEEP_FONT_SHEETS:]:
                    del font_sheets[a]
                log.warning("Exceeded maximum number of font sheets (%s)" %
                            MAX_FONT_SHEETS)
            font_sheets[asset.render()] = font_sheet
        for file_, codepoint in codepoints:
            _font_sheet_cache[file_] = codepoint
    # TODO this sometimes returns an empty list, or is never assigned to
    return asset
Exemplo n.º 43
0
 def __unicode__(self):
     return '"%s"' % escape(self.value)
Exemplo n.º 44
0
def background_noise(density=None, opacity=None, size=None, monochrome=False, intensity=None, color=None, background=None, inline=False):
    if not Image:
        raise Exception("Images manipulation require PIL")

    if isinstance(density, ListValue):
        density = [NumberValue(v).value for n, v in density.items()]
    else:
        density = [NumberValue(density).value]

    if isinstance(intensity, ListValue):
        intensity = [NumberValue(v).value for n, v in intensity.items()]
    else:
        intensity = [NumberValue(intensity).value]

    if isinstance(color, ListValue):
        color = [ColorValue(v).value for n, v in color.items() if v]
    else:
        color = [ColorValue(color).value] if color else []

    if isinstance(opacity, ListValue):
        opacity = [NumberValue(v).value for n, v in opacity.items()]
    else:
        opacity = [NumberValue(opacity).value]

    size = int(NumberValue(size).value) if size else 0
    if size < 1 or size > 512:
        size = 200

    monochrome = bool(monochrome)

    background = ColorValue(background).value if background else None

    new_image = Image.new(
        mode='RGBA',
        size=(size, size)
    )

    pixdata = new_image.load()
    _image_noise(pixdata, size, density, intensity, color, opacity, monochrome)

    if not inline:
        key = (size, density, intensity, color, opacity, monochrome)
        asset_file = 'noise-%s%sx%s' % ('mono-' if monochrome else '', size, size)
        # asset_file += '-[%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or []))
        asset_file += '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_')
        asset_file += '.png'
        asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = StringIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = 'data:image/png;base64,' + base64.b64encode(contents)

    inline = 'url("%s")' % escape(url)
    return StringValue(inline)
Exemplo n.º 45
0
def _image_url(path, only_path=False, cache_buster=True, dst_color=None, src_color=None, inline=False, mime_type=None, spacing=None, collapse_x=None, collapse_y=None):
    """
    src_color - a list of or a single color to be replaced by each corresponding dst_color colors
    spacing - spaces to be added to the image
    collapse_x, collapse_y - collapsable (layered) image of the given size (x, y)
    """
    if inline or dst_color or spacing:
        if not Image:
            raise Exception("Images manipulation require PIL")
    filepath = String.unquoted(path).value
    fileext = os.path.splitext(filepath)[1].lstrip('.').lower()
    if mime_type:
        mime_type = String.unquoted(mime_type).value
    if not mime_type:
        mime_type = mimetypes.guess_type(filepath)[0]
    if not mime_type:
        mime_type = 'image/%s' % fileext
    path = None
    IMAGES_ROOT = _images_root()
    if callable(IMAGES_ROOT):
        try:
            _file, _storage = list(IMAGES_ROOT(filepath))[0]
        except IndexError:
            filetime = None
        else:
            filetime = getmtime(_file, _storage)
        if filetime is None:
            filetime = 'NA'
        elif inline or dst_color or spacing:
            path = _storage.open(_file)
    else:
        _path = os.path.join(IMAGES_ROOT.rstrip('/'), filepath.strip('/'))
        filetime = getmtime(_path)
        if filetime is None:
            filetime = 'NA'
        elif inline or dst_color or spacing:
            path = open(_path, 'rb')

    BASE_URL = config.IMAGES_URL or config.STATIC_URL
    if path:
        dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(dst_color) if v]

        src_color = Color.from_name('black') if src_color is None else src_color
        src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(src_color)]

        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 = Number(0) if spacing is None else spacing
        spacing = [int(Number(v).value) for v in List.from_maybe(spacing)]
        spacing = (spacing * 4)[:4]

        file_name, file_ext = os.path.splitext(os.path.normpath(filepath).replace('\\', '_').replace('/', '_'))
        key = (filetime, src_color, dst_color, spacing)
        asset_file = file_name + '-' + make_filename_hash(key) + file_ext
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets')
        asset_path = os.path.join(ASSETS_ROOT, asset_file)

        if os.path.exists(asset_path):
            filepath = asset_file
            BASE_URL = config.ASSETS_URL
            if inline:
                path = open(asset_path, 'rb')
                url = make_data_url(mime_type, path.read())
            else:
                url = '%s%s' % (BASE_URL, filepath)
                if cache_buster:
                    filetime = getmtime(asset_path)
                    url = add_cache_buster(url, filetime)
        else:
            simply_process = False
            image = None

            if fileext in ('cur',):
                simply_process = True
            else:
                try:
                    image = Image.open(path)
                except IOError:
                    if not collapse_x and not collapse_y and not dst_colors:
                        simply_process = True

            if simply_process:
                if inline:
                    url = make_data_url(mime_type, path.read())
                else:
                    url = '%s%s' % (BASE_URL, filepath)
                    if cache_buster:
                        filetime = getmtime(asset_path)
                        url = add_cache_buster(url, filetime)
            else:
                width, height = collapse_x or image.size[0], collapse_y or image.size[1]
                new_image = Image.new(
                    mode='RGBA',
                    size=(width + spacing[1] + spacing[3], height + spacing[0] + spacing[2]),
                    color=(0, 0, 0, 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])
                iwidth, iheight = image.size
                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(spacing[3]), int(spacing[0])), cropped_image)
                            cx += width
                        cy += height
                else:
                    new_image.paste(image, (int(spacing[3]), int(spacing[0])))

                if not inline:
                    try:
                        new_image.save(asset_path)
                        filepath = asset_file
                        BASE_URL = config.ASSETS_URL
                        if cache_buster:
                            filetime = getmtime(asset_path)
                    except IOError:
                        log.exception("Error while saving image")
                        inline = True  # Retry inline version
                    url = os.path.join(config.ASSETS_URL.rstrip('/'), asset_file.lstrip('/'))
                    if cache_buster:
                        url = add_cache_buster(url, filetime)
                if inline:
                    output = six.BytesIO()
                    new_image.save(output, format='PNG')
                    contents = output.getvalue()
                    output.close()
                    url = make_data_url(mime_type, contents)
    else:
        url = os.path.join(BASE_URL.rstrip('/'), filepath.lstrip('/'))
        if cache_buster and filetime != 'NA':
            url = add_cache_buster(url, filetime)

    if not only_path:
        url = 'url(%s)' % escape(url)
    return String.unquoted(url)
Exemplo n.º 46
0
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`.

    $position - For `horizontal` and `vertical` directions, the position of the sprite. (defaults to `0`)
    $<sprite>-position - Position of a given sprite.

    $padding, $spacing - Adds paddings to sprites (top, right, bottom, left). (defaults to `0, 0, 0, 0`)
    $<sprite>-padding, $<sprite>-spacing - Padding for a given sprite.

    $dst-color - Together with `$src-color`, forms a map of source colors to be converted to destiny colors (same index of `$src-color` changed to `$dst-color`).
    $<sprite>-dst-color - Destiny colors for a given sprite. (defaults to `$dst-color`)

    $src-color - Selects source colors to be converted to the corresponding destiny colors. (defaults to `black`)
    $<sprite>-dst-color - Source colors for a given sprite. (defaults to `$src-color`)

    $collapse - Collapses every image in the sprite map to a fixed size (`x` and `y`).
    $collapse-x  - Collapses a size for `x`.
    $collapse-y  - Collapses a size for `y`.
    """
    if not Image:
        raise Exception("Images manipulation require PIL")

    now_time = time.time()

    g = StringValue(g).value

    if g in sprite_maps:
        sprite_maps[glob]['*'] = now_time
    elif '..' not in g:  # Protect against going to prohibited places...
        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 = [(rf[len(config.STATIC_ROOT):], s) for rf, s in files]

        if not files:
            log.error("Nothing found at '%s'", glob_path)
            return StringValue(None)

        map_name = os.path.normpath(os.path.dirname(g)).replace('\\', '_').replace('/', '_')
        key = list(zip(*files)[0]) + [repr(kwargs), config.ASSETS_URL]
        key = map_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_')
        asset_file = key + '.png'
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets')
        asset_path = os.path.join(ASSETS_ROOT, asset_file)
        cache_path = os.path.join(config.CACHE_ROOT or ASSETS_ROOT, asset_file + '.cache')

        sprite_map = None
        if os.path.exists(asset_path):
            try:
                save_time, asset, sprite_map, sizes = pickle.load(open(cache_path))
                sprite_maps[asset] = sprite_map
            except:
                pass

            if sprite_map:
                for file_, storage in files:
                    if storage is not None:
                        d_obj = storage.modified_time(file_)
                        _time = time.mktime(d_obj.timetuple())
                    else:
                        _time = os.path.getmtime(file_)
                    if save_time < _time:
                        if _time > now_time:
                            log.warning("File '%s' has a date in the future (cache ignored)" % file_)
                        sprite_map = None  # Invalidate cached sprite map
                        break

        if sprite_map is None:
            direction = StringValue(kwargs.get('direction', config.SPRTE_MAP_DIRECTION)).value
            repeat = StringValue(kwargs.get('repeat', 'no-repeat')).value
            collapse = kwargs.get('collapse') or 0
            if isinstance(collapse, List):
                collapse_x = int(NumberValue(collapse[0]).value)
                collapse_y = int(NumberValue(collapse[-1]).value)
            else:
                collapse_x = collapse_y = int(NumberValue(collapse).value)
            if 'collapse_x' in kwargs:
                collapse_x = int(NumberValue(kwargs['collapse_x']).value)
            if 'collapse_y' in kwargs:
                collapse_y = int(NumberValue(kwargs['collapse_y']).value)

            position = kwargs.get('position', 0)
            position = NumberValue(position)
            if position.unit != '%' and position.value > 1:
                position = position.value / 100.0
            else:
                position = position.value
            if position < 0:
                position = 0.0
            elif position > 1:
                position = 1.0

            padding = kwargs.get('padding', kwargs.get('spacing', 0))
            padding = [int(NumberValue(v).value) for v in List.from_maybe(padding)]
            padding = (padding * 4)[:4]

            dst_colors = kwargs.get('dst_color')
            dst_colors = [list(ColorValue(v).value[:3]) for v in List.from_maybe(dst_colors) if v]
            src_colors = kwargs.get('src_color')
            src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) for v in List.from_maybe(src_colors)]
            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]

            def images(f=lambda x: x):
                for file_, storage in f(files):
                    if storage is not None:
                        _file = storage.open(file_)
                    else:
                        _file = file_
                    _image = Image.open(_file)
                    yield _image

            names = tuple(os.path.splitext(os.path.basename(file_))[0] for file_, storage in files)

            has_dst_colors = False
            all_dst_colors = []
            all_src_colors = []
            all_positions = []
            all_paddings = []

            for name in names:
                name = name.replace('-', '_')

                _position = kwargs.get(name + '_position')
                if _position is None:
                    _position = position
                else:
                    _position = NumberValue(_position)
                    if _position.unit != '%' and _position.value > 1:
                        _position = _position.value / 100.0
                    else:
                        _position = _position.value
                    if _position < 0:
                        _position = 0.0
                    elif _position > 1:
                        _position = 1.0
                all_positions.append(_position)

                _padding = kwargs.get(name + '_padding', kwargs.get(name + '_spacing'))
                if _padding is None:
                    _padding = padding
                else:
                    _padding = [int(NumberValue(v).value) for v in List.from_maybe(_padding)]
                    _padding = (_padding * 4)[:4]
                all_paddings.append(_padding)

                _dst_colors = kwargs.get(name + '_dst_color')
                if _dst_colors is None:
                    _dst_colors = dst_colors
                    if dst_colors:
                        has_dst_colors = True
                else:
                    has_dst_colors = True
                    _dst_colors = [list(ColorValue(v).value[:3]) for v in List.from_maybe(_dst_colors) if v]
                _src_colors = kwargs.get(name + '_src_color')
                if _src_colors is None:
                    _src_colors = src_colors
                else:
                    _src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) for v in List.from_maybe(_src_colors)]
                _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]
                all_dst_colors.append(_dst_colors)
                all_src_colors.append(_src_colors)

            sizes = tuple((collapse_x or i.size[0], collapse_y or i.size[1]) for i in images())

            if direction == 'horizontal':
                layout = HorizontalSpritesLayout(sizes, all_paddings, position=all_positions)
            elif direction == 'vertical':
                layout = VerticalSpritesLayout(sizes, all_paddings, position=all_positions)
            elif direction == 'diagonal':
                layout = DiagonalSpritesLayout(sizes, all_paddings)
            elif direction == 'smart':
                layout = PackedSpritesLayout(sizes, all_paddings)
            else:
                raise Exception("Invalid direction %r" % (direction,))
            layout_positions = list(layout)

            new_image = Image.new(
                mode='RGBA',
                size=(layout.width, layout.height),
                color=(0, 0, 0, 0)
            )

            useless_dst_color = has_dst_colors

            offsets_x = []
            offsets_y = []
            for i, image in enumerate(images()):
                x, y, width, height, cssx, cssy, cssw, cssh = layout_positions[i]
                iwidth, iheight = image.size

                if has_dst_colors:
                    pixdata = image.load()
                    for _y in xrange(iheight):
                        for _x in xrange(iwidth):
                            pixel = pixdata[_x, _y]
                            a = pixel[3] if len(pixel) == 4 else 255
                            if a:
                                rgb = pixel[:3]
                                for j, dst_color in enumerate(all_dst_colors[i]):
                                    if rgb == all_src_colors[i][j]:
                                        new_color = tuple([int(c) for c in dst_color] + [a])
                                        if pixel != new_color:
                                            pixdata[_x, _y] = new_color
                                            useless_dst_color = False
                                        break

                if iwidth != width or iheight != height:
                    cy = 0
                    while cy < iheight:
                        cx = 0
                        while cx < iwidth:
                            new_image = alpha_composite(new_image, image, (x, y), (cx, cy, cx + width, cy + height))
                            cx += width
                        cy += height
                else:
                    new_image.paste(image, (x, y))
                offsets_x.append(cssx)
                offsets_y.append(cssy)

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

            try:
                new_image.save(asset_path)
            except IOError:
                log.exception("Error while saving image")

            filetime = int(now_time)
            url = '%s%s?_=%s' % (config.ASSETS_URL, asset_file, filetime)
            asset = 'url("%s") %s' % (escape(url), repeat)

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

            cache_tmp = tempfile.NamedTemporaryFile(delete=False, dir=ASSETS_ROOT)
            pickle.dump((now_time, asset, sprite_map, zip(files, sizes)), cache_tmp)
            cache_tmp.close()
            os.rename(cache_tmp.name, cache_path)

            # Use the sorted list to remove older elements (keep only 500 objects):
            if len(sprite_maps) > MAX_SPRITE_MAPS:
                for a in sorted(sprite_maps, key=lambda a: sprite_maps[a]['*'], reverse=True)[KEEP_SPRITE_MAPS:]:
                    del sprite_maps[a]
                log.warning("Exceeded maximum number of sprite maps (%s)" % MAX_SPRITE_MAPS)
            sprite_maps[asset] = sprite_map
        for file_, size in sizes:
            _image_size_cache[file_] = size
    ret = StringValue(asset)
    return ret
Exemplo n.º 47
0
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