1. import os
  2. import posixpath
  3.  
  4. from docutils.parsers.rst.directives import class_option, nonnegative_int
  5. from docutils.parsers.rst.directives.images import Figure
  6. from sphinx.addnodes import download_reference
  7. from sphinx.writers.html import HTMLTranslator
  8. from PIL import Image
  9.  
  10. THUMBNAILS_FOLDER_NAME = '_thumbnails'
  11.  
  12.  
  13. class FancyFigure(Figure):
  14.     option_spec = Figure.option_spec.copy()
  15.  
  16.     option_spec.update({
  17.         'rel': class_option,
  18.         'fitwidth': nonnegative_int,
  19.         'fitheight': nonnegative_int,
  20.         })
  21.     has_content = True
  22.  
  23.     def run(self):
  24.         dimensions = (self.options.pop('fitwidth', None), self.options.pop('fitheight', None))
  25.         if not all(dimensions):
  26.             dimensions = (200, 200)
  27.  
  28.         rel_path = os.path.split(self.arguments[0])
  29.         #self.options['target'] = self.arguments[0]
  30.  
  31.         source_root = os.path.split(self.state.document.attributes['source'])[0]
  32.  
  33.         real_path = os.path.join(source_root, *rel_path)
  34.  
  35.         tn_dir = os.path.join(source_root, THUMBNAILS_FOLDER_NAME)
  36.  
  37.         thumb_path = os.path.join(tn_dir, 'tn_' + rel_path[1])
  38.  
  39.         if not os.path.isdir(tn_dir):
  40.             if os.path.exists(tn_dir):
  41.                 raise Exception('%s must be a directory' % THUMBNAILS_FOLDER_NAME)
  42.  
  43.             os.mkdir(tn_dir)
  44.  
  45.         if not os.path.exists(real_path):
  46.             return super(FancyFigure, self).run()
  47.  
  48.         def set_size(size):
  49.             for k, v in zip(['width', 'height'], size):
  50.                 self.options[k] = str(v)
  51.  
  52.         def make_thumb():
  53.             thumb = Image.open(real_path)
  54.             thumb.thumbnail(dimensions, Image.ANTIALIAS)
  55.             set_size(thumb.size)
  56.  
  57.             thumb.save(thumb_path)
  58.  
  59.         if not os.path.exists(thumb_path) or os.path.getmtime(thumb_path) < os.path.getmtime(real_path):
  60.             make_thumb()
  61.         elif os.path.exists(thumb_path):
  62.             thumb = Image.open(thumb_path)
  63.             compare = any(i == j for i, j in zip(dimensions, thumb.size))
  64.  
  65.             if not compare:
  66.                 make_thumb()
  67.             else:
  68.                 set_size(thumb.size)
  69.  
  70.         download = download_reference(reftarget=self.arguments[0], rel=' '.join(self.options['rel']))
  71.         self.arguments[0] = os.path.relpath(thumb_path, source_root)
  72.         download += super(FancyFigure, self).run()
  73.         return [download]
  74.  
  75.  
  76. def visit_download_reference(self, node):
  77.     rel = 'rel="%s"' % node['rel'] if node.get('rel', None) else ''
  78.     if node.hasattr('filename'):
  79.         self.body.append(
  80.             '<a class="reference download internal" href="%s" %s>' %
  81.             (posixpath.join(self.builder.dlpath, node['filename']), rel))
  82.         self.context.append('</a>')
  83.     else:
  84.         self.context.append('')
  85.  
  86.  
  87. def setup(app):
  88.     app.add_node(download_reference, html=(visit_download_reference, HTMLTranslator.depart_download_reference))
  89.     app.add_config_value('thumbnails_folder', THUMBNAILS_FOLDER_NAME, 'env')
  90.     app.add_config_value('thumbnails_size', (200, 200), 'env')
  91.  
  92.     app.add_directive('fancyimage', FancyFigure)