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) | |||