Added a CSS data URI generator
Signed-off-by: Jannis Leidel <jannis@leidel.info>
This commit is contained in:
committed by
Jannis Leidel
parent
24b67475ac
commit
316033916e
@@ -18,3 +18,5 @@ if COMPRESS_CSS_FILTERS is None:
|
||||
|
||||
if COMPRESS_JS_FILTERS is None:
|
||||
COMPRESS_JS_FILTERS = []
|
||||
|
||||
COMPRESS_DATA_URI_MIN_SIZE = getattr(settings, 'COMPRESS_DATA_URI_MIN_SIZE', 1024)
|
||||
|
||||
46
compressor/filters/datauri.py
Normal file
46
compressor/filters/datauri.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import os, re
|
||||
from base64 import b64encode
|
||||
from mimetypes import guess_type
|
||||
from compressor.filters import FilterBase, FilterError
|
||||
from compressor.conf import settings
|
||||
|
||||
|
||||
class DataUriFilter(FilterBase):
|
||||
"""Filter for embedding media as data: URIs.
|
||||
|
||||
Settings:
|
||||
COMPRESS_DATA_URI_MIN_SIZE: Only files that are smaller than this
|
||||
value will be embedded. Unit; bytes.
|
||||
|
||||
|
||||
Don't use this class directly. Use a subclass.
|
||||
"""
|
||||
def input(self, filename=None, **kwargs):
|
||||
if not filename or not filename.startswith(settings.MEDIA_ROOT):
|
||||
return self.content
|
||||
output = self.content
|
||||
for url_pattern in self.url_patterns:
|
||||
output = url_pattern.sub(self.data_uri_converter, output)
|
||||
return output
|
||||
|
||||
def get_file_path(self, url):
|
||||
return os.path.join(settings.MEDIA_ROOT, url[len(settings.MEDIA_URL):])
|
||||
|
||||
def data_uri_converter(self, matchobj):
|
||||
url = matchobj.group(1).strip(' \'"')
|
||||
if not url.startswith('data:'):
|
||||
path = self.get_file_path(url)
|
||||
if os.stat(path).st_size <= settings.COMPRESS_DATA_URI_MIN_SIZE:
|
||||
data = b64encode(open(path, 'rb').read())
|
||||
return 'url("data:%s;base64,%s")' % (guess_type(path)[0], data)
|
||||
return 'url("%s")' % url
|
||||
|
||||
|
||||
class CssDataUriFilter(DataUriFilter):
|
||||
"""Filter for embedding media as data: URIs in CSS files.
|
||||
|
||||
See DataUriFilter.
|
||||
"""
|
||||
url_patterns = (
|
||||
re.compile(r'url\(([^\)]+)\)'),
|
||||
)
|
||||
@@ -137,6 +137,21 @@ class CssAbsolutizingTestCase(TestCase):
|
||||
self.assertEqual(out, self.cssNode.hunks)
|
||||
|
||||
|
||||
class CssDataUriTestCase(TestCase):
|
||||
def setUp(self):
|
||||
settings.COMPRESS = True
|
||||
settings.COMPRESS_CSS_FILTERS = ['compressor.filters.datauri.CssDataUriFilter']
|
||||
settings.MEDIA_URL = '/media/'
|
||||
self.css = """
|
||||
<link rel="stylesheet" href="/media/css/datauri.css" type="text/css" charset="utf-8">
|
||||
"""
|
||||
self.cssNode = CssCompressor(self.css)
|
||||
|
||||
def test_data_uris(self):
|
||||
out = [u'.add { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg=="); }\n.python { background-image: url("/media/img/python.png"); }\n.datauri { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0 vr4MkhoXe0rZigAAAABJRU5ErkJggg=="); }\n']
|
||||
self.assertEqual(out, self.cssNode.hunks)
|
||||
|
||||
|
||||
class CssMediaTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.css = """
|
||||
@@ -170,6 +185,7 @@ def render(template_string, context_dict=None):
|
||||
t = Template(template_string)
|
||||
return t.render(c).strip()
|
||||
|
||||
|
||||
class TemplatetagTestCase(TestCase):
|
||||
def setUp(self):
|
||||
settings.COMPRESS = True
|
||||
|
||||
3
tests/media/css/datauri.css
Normal file
3
tests/media/css/datauri.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.add { background-image: url("../img/add.png"); }
|
||||
.python { background-image: url("../img/python.png"); }
|
||||
.datauri { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0 vr4MkhoXe0rZigAAAABJRU5ErkJggg=="); }
|
||||
BIN
tests/media/img/add.png
Normal file
BIN
tests/media/img/add.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 733 B |
BIN
tests/media/img/python.png
Normal file
BIN
tests/media/img/python.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Reference in New Issue
Block a user