Repository: getnikola/nikola

Issue:
Thumbnails of SVGs are not scaled
Integrating a thumbnail with
.. thumbnail:: image.svg

leads to having the full image integrated. Also colorbox click-to-enlarge is not enabled for this image.

The 'thumbnail' being created as image.thumbnail.svg is identical to image.svg.

Possible fixes include having the svg's inside <image> tags with width/height instead of object, and manipulating the thumbnail.svg to have a different viewport.



Discussion:

Resolving patch:
diff --git a/nikola/image_processing.py b/nikola/image_processing.py
--- a/nikola/image_processing.py
+++ b/nikola/image_processing.py
@@ -29,6 +29,9 @@
 from __future__ import unicode_literals
 import datetime
 import os
+import lxml
+import re
+import gzip
 
 from nikola import utils
 
@@ -53,7 +56,7 @@ class ImageProcessor(object):
     def resize_image(self, src, dst, max_size, bigger_panoramas=True):
         """Make a copy of the image in the requested size."""
         if not Image or os.path.splitext(src)[1] in ['.svg', '.svgz']:
-            utils.copy_file(src, dst)
+            self.resize_svg(src, dst, max_size, bigger_panoramas)
             return
         im = Image.open(src)
         w, h = im.size
@@ -90,6 +93,48 @@ def resize_image(self, src, dst, max_size, bigger_panoramas=True):
         else:  # Image is small
             utils.copy_file(src, dst)
 
+    def resize_svg(self, src, dst, max_size, bigger_panoramas):
+        """Make a copy of an svg at the requested size."""
+        try:
+            # Resize svg based on viewport hacking.
+            # note that this can also lead to enlarged svgs
+            if src.endswith('.svgz'):
+                with gzip.GzipFile(src) as op:
+                    xml = op.read()
+            else:
+                with open(src) as op:
+                    xml = op.read()
+            tree = lxml.etree.XML(xml)
+            width = tree.attrib['width']
+            height = tree.attrib['height']
+            w = int(re.search("[0-9]+", width).group(0))
+            h = int(re.search("[0-9]+", height).group(0))
+            # calculate new size preserving aspect ratio.
+            ratio = float(w) / h
+            # Panoramas get larger thumbnails because they look *awful*
+            if bigger_panoramas and w > 2 * h:
+                max_size = max_size * 4
+            if w > h:
+                w = max_size
+                h = max_size / ratio
+            else:
+                w = max_size * ratio
+                h = max_size
+            w = int(w)
+            h = int(h)
+            tree.attrib.pop("width")
+            tree.attrib.pop("height")
+            tree.attrib['viewport'] = "0 0 %ipx %ipx" % (w, h)
+            if dst.endswith('.svgz'):
+                op = gzip.GzipFile(dst, 'w')
+            else:
+                op = open(dst, 'w')
+            op.write(lxml.etree.tostring(tree))
+            op.close()
+        except (KeyError, AttributeError) as e:
+            self.logger.warn("No width/height in %s. Actuall exception: %s" % (src, e))
+            utils.copy_file(src, dst)
+
     def image_date(self, src):
         """Try to figure out the date of the image."""
         if src not in self.dates:
diff --git a/nikola/plugins/compile/rest/thumbnail.py b/nikola/plugins/compile/rest/thumbnail.py
--- a/nikola/plugins/compile/rest/thumbnail.py
+++ b/nikola/plugins/compile/rest/thumbnail.py
@@ -70,8 +70,12 @@ def figwidth_value(argument):
     def run(self):
         """Run the thumbnail directive."""
         uri = directives.uri(self.arguments[0])
+        if uri.endswith('.svg'):
+            # the ? at the end makes docutil output an <img> instead of an object for the svg, which colorbox requires
+            self.arguments[0] = '.thumbnail'.join(os.path.splitext(uri)) + '?'
+        else:
+            self.arguments[0] = '.thumbnail'.join(os.path.splitext(uri))
         self.options['target'] = uri
-        self.arguments[0] = '.thumbnail'.join(os.path.splitext(uri))
         if self.content:
             (node,) = Figure.run(self)
         else:


Test patch: