This plugin adds `width` and `height` tags to `img` when they are not present. It also adds them when only one of them is present (respecting proportions).main
@@ -0,0 +1,86 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
Contains classes to handle images related things | |||||
""" | |||||
from hyde.plugin import Plugin | |||||
import re | |||||
import Image | |||||
from BeautifulSoup import BeautifulSoup | |||||
class ImageSizerPlugin(Plugin): | |||||
""" | |||||
Each HTML page is modified to add width and height for images if | |||||
they are not already specified. | |||||
""" | |||||
def __init__(self, site): | |||||
super(ImageSizerPlugin, self).__init__(site) | |||||
self.cache = {} | |||||
def text_resource_complete(self, resource, text): | |||||
""" | |||||
When the resource is generated, search for img tag and specify | |||||
their sizes. | |||||
""" | |||||
try: | |||||
mode = self.site.config.mode | |||||
except AttributeError: | |||||
mode = "production" | |||||
if not resource.source_file.kind == 'html': | |||||
return | |||||
if mode.startswith('dev'): | |||||
self.logger.debug("Skipping sizer in development mode.") | |||||
return | |||||
soup = BeautifulSoup(text) | |||||
for img in soup.findAll('img'): | |||||
if img.has_key('width') and img.has_key('height'): | |||||
continue | |||||
if not img.has_key('src'): | |||||
self.logger.warn("[%s] has an img tag without src attribute" % resource) | |||||
continue | |||||
if not img['src'] in self.cache: | |||||
if not re.match(r"(/[^/]|[^/]).*", img['src']): | |||||
# Not a local link | |||||
continue | |||||
if img['src'].startswith("/"): | |||||
# Absolute resource | |||||
path = img['src'].lstrip("/") | |||||
image = self.site.content.resource_from_relative_deploy_path(path) | |||||
else: | |||||
# Relative resource | |||||
path = resource.node.source_folder.child(img['src']) | |||||
image = self.site.content.resource_from_path(path) | |||||
if image is None: | |||||
self.logger.warn( | |||||
"[%s] has an unknown image" % resource) | |||||
continue | |||||
if image.source_file.kind not in ['png', 'jpg', 'jpeg', 'gif']: | |||||
self.logger.warn( | |||||
"[%s] has an img tag not linking to an image" % resource) | |||||
continue | |||||
# Now, get the size of the image | |||||
try: | |||||
self.cache[img['src']] = Image.open(image.path).size | |||||
except IOError: | |||||
self.logger.warn( | |||||
"Unable to process image [%s]" % image) | |||||
self.cache[img['src']] = (None, None) | |||||
continue | |||||
self.logger.debug("Image [%s] is %s" % (img['src'], | |||||
self.cache[img['src']])) | |||||
width, height = self.cache[img['src']] | |||||
if width is None: | |||||
continue | |||||
if img.has_key('width'): | |||||
height = int(img['width'])*height/width | |||||
width = int(img['width']) | |||||
elif img.has_key('height'): | |||||
width = int(img['height'])*width/height | |||||
height = int(img['height']) | |||||
img['height'], img['width'] = height, width | |||||
return unicode(soup) |
@@ -0,0 +1,84 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
Use nose | |||||
`$ pip install nose` | |||||
`$ nosetests` | |||||
""" | |||||
from hyde.fs import File, Folder | |||||
from hyde.generator import Generator | |||||
from hyde.site import Site | |||||
from pyquery import PyQuery | |||||
TEST_SITE = File(__file__).parent.parent.child_folder('_test') | |||||
IMAGE_SOURCE = File(__file__).parent.child_folder('optipng') | |||||
IMAGE_NAME = "hyde-lt-b.png" | |||||
IMAGE_SIZE = (538, 132) | |||||
class TestImageSizer(object): | |||||
def setUp(self): | |||||
TEST_SITE.make() | |||||
TEST_SITE.parent.child_folder( | |||||
'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
IMAGES = TEST_SITE.child_folder('content/media/img') | |||||
IMAGES.make() | |||||
IMAGE_SOURCE.copy_contents_to(IMAGES) | |||||
def tearDown(self): | |||||
TEST_SITE.delete() | |||||
def _generic_test_image(self, text): | |||||
site = Site(TEST_SITE) | |||||
site.config.mode = "production" | |||||
site.config.plugins = ['hyde.ext.plugins.images.ImageSizerPlugin'] | |||||
tlink = File(site.content.source_folder.child('timg.html')) | |||||
tlink.write(text) | |||||
gen = Generator(site) | |||||
gen.generate_all() | |||||
f = File(site.config.deploy_root_path.child(tlink.name)) | |||||
assert f.exists | |||||
html = f.read_all() | |||||
assert html | |||||
print html | |||||
return html | |||||
def test_size_image(self): | |||||
text = u""" | |||||
<img src="/media/img/%s"> | |||||
""" % IMAGE_NAME | |||||
html = self._generic_test_image(text) | |||||
assert ' width="%d"' % IMAGE_SIZE[0] in html | |||||
assert ' height="%d"' % IMAGE_SIZE[1] in html | |||||
def test_size_image_relative(self): | |||||
text = u""" | |||||
<img src="media/img/%s"> | |||||
""" % IMAGE_NAME | |||||
html = self._generic_test_image(text) | |||||
assert ' width="%d"' % IMAGE_SIZE[0] in html | |||||
assert ' height="%d"' % IMAGE_SIZE[1] in html | |||||
def test_size_image_no_resize(self): | |||||
text = u""" | |||||
<img src="/media/img/%s" width="2000" height="150"> | |||||
""" % IMAGE_NAME | |||||
html = self._generic_test_image(text) | |||||
assert ' width="%d"' % IMAGE_SIZE[0] not in html | |||||
assert ' height="%d"' % IMAGE_SIZE[1] not in html | |||||
def test_size_image_size_proportional(self): | |||||
text = u""" | |||||
<img src="/media/img/%s" width="%d"> | |||||
""" % (IMAGE_NAME, IMAGE_SIZE[0]*2) | |||||
html = self._generic_test_image(text) | |||||
assert ' width="%d"' % (IMAGE_SIZE[0]*2) in html | |||||
assert ' height="%d"' % (IMAGE_SIZE[1]*2) in html | |||||
def test_size_image_not_exists(self): | |||||
text = u""" | |||||
<img src="/media/img/hyde-logo-no.png"> | |||||
""" | |||||
html = self._generic_test_image(text) | |||||