Issue #883 closed

img "scale" option is broken for HTML output

tpowers avatartpowers created an issue

BUG: When trying to scale HTML images, The _images outdir is *NOT* populated at the time BaseTranslator.visit_image() is called.

HTML image scaling used to work about a year ago. Looking at the repository, I don't see what broke things.

Here's the details, in Sphinx-1.1.2-py2.7.egg/sphinx/writers/html.py:

    def visit_image(self, node):
        olduri = node['uri']
        # rewrite the URI if the environment knows about it
        if olduri in self.builder.images:
            node['uri'] = posixpath.join(self.builder.imgpath,
                                         self.builder.images[olduri])
        ...
        if node.has_key('scale'):
            if Image and not (node.has_key('width')
                              and node.has_key('height')):
                try:
                    im = Image.open(os.path.join(self.builder.srcdir, olduri))
                except (IOError, # Source image can't be found or opened
                        UnicodeError):  # PIL doesn't like Unicode paths.
                    pass
                else:
                    if not node.has_key('width'):
                        node['width'] = str(im.size[0])
                    if not node.has_key('height'):
                        node['height'] = str(im.size[1])
                    del im
        BaseTranslator.visit_image(self, node)

Just before the BaseTranslator.visit_image() call, using the debugger we see:

   >>> self.builder.srcdir
   'C:\\Sphinx'
   >>>self.builder.outdir
   'C:\\Sphinx\\_build\\html'
   >>> olduri
   u'images/dependency_walker.png'
   >>> node['uri']
   u'_images/dependency_walker.png'
   >>> os.getcwd()
   'C:\\Sphinx'

And in docutils-0.8.1-py2.7.egg/docutils/writers/html4css1/init.py:

   def visit_image(self, node):
        atts = {}
        uri = node['uri']
      ...
        if 'scale' in node:
            if Image and not ('width' in node and 'height' in node):
                try:
                    im = Image.open(str(uri))
                except (IOError, # Source image can't be found or opened
                        UnicodeError):  # PIL doesn't like Unicode paths.
                    pass
                else:
                    if 'width' not in atts:
                        atts['width'] = str(im.size[0])
                    if 'height' not in atts:
                        atts['height'] = str(im.size[1])
                    del im
            for att_name in 'width', 'height':
                if att_name in atts:
                    match = re.match(r'([0-9.]+)(\S*)$', atts[att_name])
                    assert match
                    atts[att_name] = '%s%s' % (
                        float(match.group(1)) * (float(node['scale']) / 100),
                        match.group(2))

When this statement happens:

   im = Image.open(str(uri))

the debugger shows;

   >>> uri
   u'_images/dependency_walker.png'
   >>> os.getcwd()
   'C:\\Sphinx'

Which is what the correct uri WILL be, but if you look at 'C:\Sphinx\_build\html' at this point you see .html files and a _sources directory but NO _images directory. So, the Image.open() call fails, and the scaling doesn't occur.

Since Sphinx can't change the docutils source, the solution is the _images outdir has to be populated before calling BaseTranslator.visit_image(). But I don't know how to do that?

The workaround is to use the :height: or :width: img options instead of :scale:, since using those options doesn't require opening the image first.

Comments (8)

  1. Georg Brandl

    But the code in Sphinx' visit_image is exactly there for this reason: it figures out height and width on its own (with the correct file name) so that docutils doesn't have to. So this cannot be the reason why scaling doesn't work for you (btw, it does for me here.)

    Can you make a reproducible test case?

  2. Anonymous

    Ah, the problem is the following:

        if Image and not (node.has_key('width')
                          and node.has_key('height')):
    

    When I look at this in the debugger, Image is None. It looks like when easy-install is used to install PIL then the following doesn't work:

       from PIL import Image 
    

    but

        import Image
    

    does work. So a fix is to change Sphinx-1.1.2-py2.7.egg/sphinx/writers/html.py:

        try:
            from PIL import Image        # check for the Python Imaging Library
        except ImportError:
            Image = None
    

    to:

    try:
        import Image                        # check for the Python Imaging Library
    except ImportError:
        Image = None
    

    which is what docutils-0.8.1-py2.7.egg/docutils/writers/html4css1/init.py does. Then the :scale: option works as expected.

    As noted elsewhere (and confirmed by looking at Sphinx-1.1.3-py2.7.egg\EGG-INFO\requires.txt), easy-install doesn't automatically add PIL when it installs Sphinx (and neither does docutils). Maybe this is a good thing but then the documentation should say near the discussion of the image directive that you need to manually install PIL if you want the :scale: option to work. This is a slightly subtle bug since images seem to work fine until you try the :scale: option.

  3. Christoph Deil

    I ran into the following error trying to build the scikit-learn sphinx docu. How can I check what the origin of the issue is? There are :scale: commands in the rst source, but I'm not sure if this is the problem.

    Is it covered by this ticket or should I file a separate docutils or sphinx ticket?

    # Sphinx version: 1.1.3
    # Python version: 2.7.3
    # Docutils version: 0.9 release
    # Jinja2 version: 2.6
    Traceback (most recent call last):
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sphinx/cmdline.py", line 189, in main
        app.build(force_all, filenames)
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sphinx/application.py", line 204, in build
        self.builder.build_update()
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sphinx/builders/__init__.py", line 196, in build_update
        'out of date' % len(to_build))
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sphinx/builders/__init__.py", line 252, in build
        self.write(docnames, list(updated_docnames), method)
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sphinx/builders/__init__.py", line 292, in write
        self.write_doc(docname, doctree)
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sphinx/builders/html.py", line 419, in write_doc
        self.docwriter.write(doctree, destination)
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/docutils/writers/__init__.py", line 77, in write
        self.translate()
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sphinx/writers/html.py", line 38, in translate
        self.document.walkabout(visitor)
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/docutils/nodes.py", line 173, in walkabout
        if child.walkabout(visitor):
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/docutils/nodes.py", line 173, in walkabout
        if child.walkabout(visitor):
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/docutils/nodes.py", line 173, in walkabout
        if child.walkabout(visitor):
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/docutils/nodes.py", line 173, in walkabout
        if child.walkabout(visitor):
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/docutils/nodes.py", line 165, in walkabout
        visitor.dispatch_visit(self)
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/docutils/nodes.py", line 1611, in dispatch_visit
        return method(node)
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sphinx/writers/html.py", line 377, in visit_image
        BaseTranslator.visit_image(self, node)
      File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/docutils/writers/html4css1/__init__.py", line 1026, in visit_image
        and self.settings.file_insertion_enabled):
    AttributeError: Values instance has no attribute 'file_insertion_enabled'
    
  4. Ralf Gommers

    Ran into this building scipy-lecture-notes. I confirm that the fix from Anonymous works for me. If you install PIL with easy_install, then

    from PIL import Image

    doesn't work, but

    import Image

    does. See http://stackoverflow.com/questions/8339991/why-in-python-sometimes-from-pil-import-image-fails-and-import-image-works for why.

    However, the right fix is not to replace one import with the other, but support both. Because which one works is system dependent. More robust fix:

    try: from PIL import Image # check for the Python Imaging Library except ImportError: try: import Image # other way of importing PIL except ImportError: Image = None

    Christoph, this fixes the scikit-learn doc for me.

  5. Ralf Gommers

    Apologies for the messed up formatting.

    try: 
        from PIL import Image # check for the Python Imaging Library
    except ImportError:
        try: 
            import Image # other way of importing PIL 
        except ImportError: 
            Image = None
    
  6. space one

    hmm, i added the import Image thing, i alos added the workaround from issue #1004 but i am still getting the same traceback as the one from the comment above.

  7. Jim Snavely

    I just got bit by this too. :scale: doesn't work. I scratched my head for a while trying to get it to work before finding this bug report. I ended up just using :width:

  8. Takayuki Shimizukawa

    This issue is tangled enough.

    • ticket #1004 no attribute 'file_insertion_enabled' error has been fixed and has been released in Sphinx-1.2.

    • ticket #920 can't import PIL error has been fixed and has been released in Sphinx-1.2.

    Unfortunately, Sphinx-1.1.3 might have this issue and we don't have a release plan for Sphinx-1.1.4.

    Please use Sphinx-1.2 or later and newest Pillow the successor of PIL.

    If you still have a issue or you need some help, please create another ticket with your using versions and environment information.

    Many thanks for your reporting and your kind cooperation!

  9. Log in to comment
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.