Georg Brandl avatar Georg Brandl committed 9826ac3

#309: The ``graphviz`` extension can now output SVG instead of PNG
images, controlled by the ``graphviz_output_format`` config value.
Patch by Henrique Bastos.

Comments (0)

Files changed (4)

 
 Other contributors, listed alphabetically, are:
 
+* Henrique Bastos -- SVG support for graphviz extension
 * Daniel Bültmann -- todo extension
 * Michael Droettboom -- inheritance_diagram extension
 * Charles Duffy -- original graphviz extension
 
 * Added Epub builder.
 
+* #309: The ``graphviz`` extension can now output SVG instead of PNG
+  images, controlled by the ``graphviz_output_format`` config value.
+
 * #284: All docinfo metadata is now put into the document metadata, not
   just the author.
 

doc/ext/graphviz.rst

             "bar" -> "baz";
          }
 
-   In HTML output, the code will be rendered to a PNG image.  In LaTeX output,
-   the code will be rendered to an embeddable PDF file.
+   In HTML output, the code will be rendered to a PNG or SVG image (see
+   :confval:`graphviz_output_format`).  In LaTeX output, the code will be
+   rendered to an embeddable PDF file.
 
 
 .. directive:: graph
    Additional command-line arguments to give to dot, as a list.  The default is
    an empty list.  This is the right place to set global graph, node or edge
    attributes via dot's ``-G``, ``-N`` and ``-E`` options.
+
+.. confval:: graphviz_output_format
+
+   The output format for Graphviz when building HTML files.  This must be either
+   ``'png'`` or ``'svg'``; the default is ``'png'``.
+
+   .. versionadded:: 1.0
+      Previously, output always was PNG.

sphinx/ext/graphviz.py

 import re
 import posixpath
 from os import path
+from math import ceil
 from subprocess import Popen, PIPE
 try:
     from hashlib import sha1 as sha
 
 
 mapname_re = re.compile(r'<map id="(.*?)"')
+svg_dim_re = re.compile(r'<svg\swidth="(\d+)pt"\sheight="(\d+)pt"', re.M)
 
 
 class GraphvizError(SphinxError):
     return relfn, outfn
 
 
+def get_svg_tag(svgref, svgfile, imgcls=None):
+    # Webkit can't figure out svg dimensions when using object tag
+    # so we need to get it from the svg file
+    fp = open(svgfile, 'r')
+    try:
+        for line in fp:
+            match = svg_dim_re.match(line)
+            if match:
+                dimensions = match.groups()
+                break
+        else:
+            dimensions = None
+    finally:
+        fp.close()
+
+    # We need this hack to make WebKit show our object tag properly
+    def pt2px(x):
+        return int(ceil((96.0/72.0) * float(x)))
+
+    if dimensions:
+        style = ' width="%s" height="%s"' % tuple(map(pt2px, dimensions))
+    else:
+        style = ''
+
+    # The object tag works fine on Firefox and WebKit
+    # Besides it's a hack, this strategy does not mess with templates.
+    imgcss = imgcls and ' class="%s"' % imgcls or ''
+    return '<object type="image/svg+xml" data="%s"%s%s/>\n' % \
+           (svgref, imgcss, style)
+
+
 def render_dot_html(self, node, code, options, prefix='graphviz', imgcls=None):
+    format = self.builder.config.graphviz_output_format
     try:
-        fname, outfn = render_dot(self, code, options, 'png', prefix)
+        if format not in ('png', 'svg'):
+            raise GraphvizError("graphviz_output_format must be one of 'png', "
+                                "'svg', but is %r" % format)
+        fname, outfn = render_dot(self, code, options, format, prefix)
     except GraphvizError, exc:
         self.builder.warn('dot code %r: ' % code + str(exc))
         raise nodes.SkipNode
     if fname is None:
         self.body.append(self.encode(code))
     else:
-        mapfile = open(outfn + '.map', 'rb')
-        try:
-            imgmap = mapfile.readlines()
-        finally:
-            mapfile.close()
-        imgcss = imgcls and 'class="%s"' % imgcls or ''
-        if len(imgmap) == 2:
-            # nothing in image map (the lines are <map> and </map>)
-            self.body.append('<img src="%s" alt="%s" %s/>\n' %
-                             (fname, self.encode(code).strip(), imgcss))
+        if format == 'svg':
+            svgtag = get_svg_tag(fname, outfn, imgcls)
+            self.body.append(svgtag)
         else:
-            # has a map: get the name of the map and connect the parts
-            mapname = mapname_re.match(imgmap[0]).group(1)
-            self.body.append('<img src="%s" alt="%s" usemap="#%s" %s/>\n' %
-                             (fname, self.encode(code).strip(),
-                              mapname, imgcss))
-            self.body.extend(imgmap)
+            mapfile = open(outfn + '.map', 'rb')
+            try:
+                imgmap = mapfile.readlines()
+            finally:
+                mapfile.close()
+            imgcss = imgcls and 'class="%s"' % imgcls or ''
+            if len(imgmap) == 2:
+                # nothing in image map (the lines are <map> and </map>)
+                self.body.append('<img src="%s" alt="%s" %s/>\n' %
+                                 (fname, self.encode(code).strip(), imgcss))
+            else:
+                # has a map: get the name of the map and connect the parts
+                mapname = mapname_re.match(imgmap[0]).group(1)
+                self.body.append('<img src="%s" alt="%s" usemap="#%s" %s/>\n' %
+                                 (fname, self.encode(code).strip(),
+                                  mapname, imgcss))
+                self.body.extend(imgmap)
+
     self.body.append('</p>\n')
     raise nodes.SkipNode
 
     app.add_directive('digraph', GraphvizSimple)
     app.add_config_value('graphviz_dot', 'dot', 'html')
     app.add_config_value('graphviz_dot_args', [], 'html')
+    app.add_config_value('graphviz_output_format', 'png', 'html')
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.