Use blockdiag to create the Heidi box diagrams.
authorGlenn Hutchings <zondo42@gmail.com>
Thu, 14 Apr 2016 19:38:24 +0000 (20:38 +0100)
committerGlenn Hutchings <zondo42@gmail.com>
Thu, 14 Apr 2016 19:38:24 +0000 (20:38 +0100)
21 files changed:
README.md
chapters/04.rst
conf.py
figures/heidiobj1.diag [new file with mode: 0644]
figures/heidiobj2.diag [new file with mode: 0644]
figures/heidiobj3.diag [new file with mode: 0644]
figures/heidiobj4.diag [new file with mode: 0644]
figures/heidiobj5.diag [new file with mode: 0644]
figures/heidiobj6.diag [new file with mode: 0644]
figures/heidiobj7.diag [new file with mode: 0644]
figures/heidiobj8.diag [new file with mode: 0644]
images/heidiobj1.png [deleted file]
images/heidiobj2.png [deleted file]
images/heidiobj3.png [deleted file]
images/heidiobj4.png [deleted file]
images/heidiobj5.png [deleted file]
images/heidiobj6.png [deleted file]
images/heidiobj7.png [deleted file]
images/heidiobj8.png [deleted file]
tools/__init__.py [new file with mode: 0644]
tools/blockdiag.py [new file with mode: 0644]

index e90ea9345bedf700d090ac1767bc232fa7507c1c..eb05a28441f8bb27bab0be603111494d2d4f604e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -7,9 +7,14 @@ Introduction
 This repository contains the sources for the
 [Inform Beginner's Guide](http://www.firthworks.com/roger/IBG.html),
 henceforth known as the IBG.  This version is in a new source format that's
-more contributor-friendly (see below).  It's a work-in-progress; see the
-[issue tracker](https://github.com/i6/ibg/issues) for how it's going.
+more version-control and contributor-friendly:
+[Sphinx](http://sphinx-doc.org), a documentation build tool written in
+[Python](http://python.org).  With Sphinx, the source files are in a very
+readable format called
+[reStructuredText](https://en.wikipedia.org/wiki/ReStructuredText).
 
+This version of the IBG is a work-in-progress; see the
+[issue tracker](https://github.com/i6/ibg/issues) for how it's going.
 Initially it will be a straight conversion of the 3rd edition.  After that,
 the 4th edition will be prepared.  The rationale for a 4th edition is:
 
@@ -25,13 +30,19 @@ the 4th edition will be prepared.  The rationale for a 4th edition is:
 Building from source
 --------------------
 
-The new source format is [Sphinx](http://sphinx-doc.org), a documentation
-build tool written in [Python](http://python.org), which you will need to
-install if you want to build the document.  You can find the complete
-installation instructions
-[here](http://www.sphinx-doc.org/en/stable/install.html).
+As well as these document sources, you will need:
+
+* Sphinx.  You can find the complete installation instructions
+  [here](http://www.sphinx-doc.org/en/stable/install.html).
+
+* Sphinx makes use of [Blockdiag](https://pypi.python.org/pypi/blockdiag)
+  for some of the diagrams; you'll need that too.
+
+If you have `pip`, this command will be all you need:
+
+    pip install sphinx blockdiag
 
-After you have Sphinx installed, you can build the HTML version of the
+After you have everything installed, you can build the HTML version of the
 guide from a command prompt, like this:
 
        make html
index 8c6a7eda2b2e748d7e0a4b780cab072cbafd5a13..d9949bbf131b9d65a3bf8b6075c6cf7a7fb207b8 100644 (file)
@@ -284,45 +284,57 @@ This causes another change in the relationships.  The bird is now a child
 of the player (and *not* of the forest), and the player is both a parent
 (of the bird) and a child (of the forest).
 
-In this diagram, we show how the object relationships change during the
-course of the game.  The straight lines represent parent--child
-relationships, with the parent object at the top of the line, and the child
-object at the bottom.
+Here we show how the object relationships change during the course of the
+game.  The straight lines represent parent--child relationships, with the
+parent object at the top of the line, and the child object at the bottom.
 
-.. list-table::
-   :widths: 1 3 5
+1. At the start of the game:
 
-   * - 1.
-     - At the start of the game:
-     - .. image:: /images/heidiobj1.*
+   .. blockdiag:: /figures/heidiobj1.diag
+      :align: center
+      :scale: 80%
 
-   * - 2.
-     - The player types: ``GO EAST``
-     - .. image:: /images/heidiobj2.*
+2. The player types: ``GO EAST``
 
-   * - 3.
-     - The player types: ``TAKE THE BIRD``
-     - .. image:: /images/heidiobj3.*
+   .. blockdiag:: /figures/heidiobj2.diag
+      :align: center
+      :scale: 80%
 
-   * - 4.
-     - The player types: ``GO NORTHEAST``
-     - .. image:: /images/heidiobj4.*
+3. The player types: ``TAKE THE BIRD``
 
-   * - 5.
-     - The player types: ``PUT BIRD IN NEST``
-     - .. image:: /images/heidiobj5.*
+   .. blockdiag:: /figures/heidiobj3.diag
+      :align: center
+      :scale: 80%
 
-   * - 6.
-     - The player types: ``TAKE NEST``
-     - .. image:: /images/heidiobj6.*
+4. The player types: ``GO NORTHEAST``
 
-   * - 7.
-     - The player types: ``UP``
-     - .. image:: /images/heidiobj7.*
+   .. blockdiag:: /figures/heidiobj4.diag
+      :align: center
+      :scale: 80%
 
-   * - 8.
-     - The player types: ``PUT NEST ON BRANCH``
-     - .. image:: /images/heidiobj8.*
+5. The player types: ``PUT BIRD IN NEST``
+
+   .. blockdiag:: /figures/heidiobj5.diag
+      :align: center
+      :scale: 80%
+
+6. The player types: ``TAKE NEST``
+
+   .. blockdiag:: /figures/heidiobj6.diag
+      :align: center
+      :scale: 80%
+
+7. The player types: ``UP``
+
+   .. blockdiag:: /figures/heidiobj7.diag
+      :align: center
+      :scale: 80%
+
+8. The player types: ``PUT NEST ON BRANCH``
+
+   .. blockdiag:: /figures/heidiobj8.diag
+      :align: center
+      :scale: 80%
 
 In this short example, we've taken a lot of time and space to spell out
 exactly how the objects relationship patterns -- generally known as the
diff --git a/conf.py b/conf.py
index 402eb1c74da80ee0bd6b5adca3b68e4e06044587..7546c7521c18936f4d56ae803ab097280522dbcf 100644 (file)
--- a/conf.py
+++ b/conf.py
@@ -20,9 +20,9 @@ import codecs
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.insert(0, os.path.abspath('tools'))
+sys.path.insert(0, ".")
 
-from transcript import TranscriptLexer
+from tools.transcript import TranscriptLexer
 
 # Setup function.
 def setup(app):
@@ -38,6 +38,7 @@ def setup(app):
 # ones.
 extensions = [
     'sphinx.ext.todo',
+    'tools.blockdiag',
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -331,3 +332,30 @@ texinfo_documents = [
 
 # If true, do not generate a @detailmenu in the "Top" node's menu.
 #texinfo_no_detailmenu = False
+
+
+# -- Options for block diagrams -------------------------------------------
+
+# The paths to truetype fonts. blockdiag_fontpath option accepts both
+# single path string and list of paths.
+blockdiag_fontpath = []
+
+# The path to fontmap definitions.
+blockdiag_fontmap = ""
+
+# Render diagrams in antialias mode or not.
+blockdiag_antialias = True
+
+# Render diagrams as transparency or not.
+blockdiag_transparency = True
+
+# The output image format at generating HTML docs ("PNG" or "SVG").
+blockdiag_html_image_format = "SVG"
+
+# The output image format at generating PDF docs (through LaTeX). ("PNG" or
+# "PDF").  When a value of PDF is specified, you can get clear diagram
+# images. In which case, reportlab library is required.
+blockdiag_latex_image_format = "PNG"
+
+# Enable debug mode of blockdiag.
+blockdiag_debug = False
diff --git a/figures/heidiobj1.diag b/figures/heidiobj1.diag
new file mode 100644 (file)
index 0000000..3e18718
--- /dev/null
@@ -0,0 +1,23 @@
+// 1. At the start of the game
+
+blockdiag {
+    default_shape = roundedbox;
+    orientation = portrait;
+    node_width = 80;
+
+    BC [label = "before_\ncottage"];
+    F  [label = "forest"];
+    C  [label = "clearing"];
+    TT [label = "top_of_\ntree"];
+
+    P  [label = "player"];
+    B  [label = "bird"];
+    N  [label = "nest"];
+    T  [label = "tree"];
+    TB [label = "branch"];
+
+    BC -> P;
+    F  -> B;
+    C  -> N, T;
+    TT -> TB;
+}
diff --git a/figures/heidiobj2.diag b/figures/heidiobj2.diag
new file mode 100644 (file)
index 0000000..8564db5
--- /dev/null
@@ -0,0 +1,23 @@
+// 2. The player types: ``GO EAST``
+
+blockdiag {
+    default_shape = roundedbox;
+    orientation = portrait;
+    node_width = 80;
+
+    BC [label = "before_\ncottage"];
+    F  [label = "forest"];
+    C  [label = "clearing"];
+    TT [label = "top_of_\ntree"];
+
+    P  [label = "player"];
+    B  [label = "bird"];
+    N  [label = "nest"];
+    T  [label = "tree"];
+    TB [label = "branch"];
+
+    BC;
+    F  -> P, B;
+    C  -> N, T;
+    TT -> TB;
+}
diff --git a/figures/heidiobj3.diag b/figures/heidiobj3.diag
new file mode 100644 (file)
index 0000000..c7c41f9
--- /dev/null
@@ -0,0 +1,23 @@
+// 3. The player types: ``TAKE THE BIRD``
+
+blockdiag {
+    default_shape = roundedbox;
+    orientation = portrait;
+    node_width = 80;
+
+    BC [label = "before_\ncottage"];
+    F  [label = "forest"];
+    C  [label = "clearing"];
+    TT [label = "top_of_\ntree"];
+
+    P  [label = "player"];
+    B  [label = "bird"];
+    N  [label = "nest"];
+    T  [label = "tree"];
+    TB [label = "branch"];
+
+    BC;
+    F  -> P -> B;
+    C  -> N, T;
+    TT -> TB;
+}
diff --git a/figures/heidiobj4.diag b/figures/heidiobj4.diag
new file mode 100644 (file)
index 0000000..a4dbdf8
--- /dev/null
@@ -0,0 +1,24 @@
+// 4. The player types: ``GO NORTHEAST``
+
+blockdiag {
+    default_shape = roundedbox;
+    orientation = portrait;
+    node_width = 80;
+
+    BC [label = "before_\ncottage"];
+    F  [label = "forest"];
+    C  [label = "clearing"];
+    TT [label = "top_of_\ntree"];
+
+    P  [label = "player"];
+    B  [label = "bird"];
+    N  [label = "nest"];
+    T  [label = "tree"];
+    TB [label = "branch"];
+
+    BC;
+    F;
+    C  -> P -> B;
+    C  -> N, T;
+    TT -> TB;
+}
diff --git a/figures/heidiobj5.diag b/figures/heidiobj5.diag
new file mode 100644 (file)
index 0000000..c6be0d8
--- /dev/null
@@ -0,0 +1,24 @@
+// 5. The player types: ``PUT BIRD IN NEST``
+
+blockdiag {
+    default_shape = roundedbox;
+    orientation = portrait;
+    node_width = 80;
+
+    BC [label = "before_\ncottage"];
+    F  [label = "forest"];
+    C  [label = "clearing"];
+    TT [label = "top_of_\ntree"];
+
+    P  [label = "player"];
+    B  [label = "bird"];
+    N  [label = "nest"];
+    T  [label = "tree"];
+    TB [label = "branch"];
+
+    BC;
+    F;
+    C  -> N -> B;
+    C  -> P, T;
+    TT -> TB;
+}
diff --git a/figures/heidiobj6.diag b/figures/heidiobj6.diag
new file mode 100644 (file)
index 0000000..a1d915a
--- /dev/null
@@ -0,0 +1,24 @@
+// 6. The player types: ``TAKE NEST``
+
+blockdiag {
+    default_shape = roundedbox;
+    orientation = portrait;
+    node_width = 80;
+
+    BC [label = "before_\ncottage"];
+    F  [label = "forest"];
+    C  [label = "clearing"];
+    TT [label = "top_of_\ntree"];
+
+    P  [label = "player"];
+    B  [label = "bird"];
+    N  [label = "nest"];
+    T  [label = "tree"];
+    TB [label = "branch"];
+
+    BC;
+    F;
+    C  -> P -> N -> B;
+    C  -> T;
+    TT -> TB;
+}
diff --git a/figures/heidiobj7.diag b/figures/heidiobj7.diag
new file mode 100644 (file)
index 0000000..74628f0
--- /dev/null
@@ -0,0 +1,24 @@
+// 7. The player types: ``UP``
+
+blockdiag {
+    default_shape = roundedbox;
+    orientation = portrait;
+    node_width = 80;
+
+    BC [label = "before_\ncottage"];
+    F  [label = "forest"];
+    C  [label = "clearing"];
+    TT [label = "top_of_\ntree"];
+
+    P  [label = "player"];
+    B  [label = "bird"];
+    N  [label = "nest"];
+    T  [label = "tree"];
+    TB [label = "branch"];
+
+    BC;
+    F;
+    C  -> T;
+    TT -> P -> N -> B;
+    TT -> TB;
+}
diff --git a/figures/heidiobj8.diag b/figures/heidiobj8.diag
new file mode 100644 (file)
index 0000000..edd792b
--- /dev/null
@@ -0,0 +1,24 @@
+// 8. The player types: ``PUT NEST ON BRANCH``
+
+blockdiag {
+    default_shape = roundedbox;
+    orientation = portrait;
+    node_width = 80;
+
+    BC [label = "before_\ncottage"];
+    F  [label = "forest"];
+    C  [label = "clearing"];
+    TT [label = "top_of_\ntree"];
+
+    P  [label = "player"];
+    B  [label = "bird"];
+    N  [label = "nest"];
+    T  [label = "tree"];
+    TB [label = "branch"];
+
+    BC;
+    F;
+    C  -> T;
+    TT -> P;
+    TT -> TB -> N -> B; 
+}
diff --git a/images/heidiobj1.png b/images/heidiobj1.png
deleted file mode 100644 (file)
index 6caa0e3..0000000
Binary files a/images/heidiobj1.png and /dev/null differ
diff --git a/images/heidiobj2.png b/images/heidiobj2.png
deleted file mode 100644 (file)
index 255a32a..0000000
Binary files a/images/heidiobj2.png and /dev/null differ
diff --git a/images/heidiobj3.png b/images/heidiobj3.png
deleted file mode 100644 (file)
index bfd73a5..0000000
Binary files a/images/heidiobj3.png and /dev/null differ
diff --git a/images/heidiobj4.png b/images/heidiobj4.png
deleted file mode 100644 (file)
index 082d70d..0000000
Binary files a/images/heidiobj4.png and /dev/null differ
diff --git a/images/heidiobj5.png b/images/heidiobj5.png
deleted file mode 100644 (file)
index aef4113..0000000
Binary files a/images/heidiobj5.png and /dev/null differ
diff --git a/images/heidiobj6.png b/images/heidiobj6.png
deleted file mode 100644 (file)
index 985fe15..0000000
Binary files a/images/heidiobj6.png and /dev/null differ
diff --git a/images/heidiobj7.png b/images/heidiobj7.png
deleted file mode 100644 (file)
index 46ab2b7..0000000
Binary files a/images/heidiobj7.png and /dev/null differ
diff --git a/images/heidiobj8.png b/images/heidiobj8.png
deleted file mode 100644 (file)
index c4ade78..0000000
Binary files a/images/heidiobj8.png and /dev/null differ
diff --git a/tools/__init__.py b/tools/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/blockdiag.py b/tools/blockdiag.py
new file mode 100644 (file)
index 0000000..1aca0cf
--- /dev/null
@@ -0,0 +1,334 @@
+# -*- coding: utf-8 -*-
+"""
+    blockdiag.sphinx_ext
+    ~~~~~~~~~~~~~~~~~~~~
+
+    Allow blockdiag-formatted diagrams to be included in Sphinx-generated
+    documents inline.
+
+    :copyright: Copyright 2010 by Takeshi Komiya.
+    :license: BSDL.
+"""
+
+from __future__ import absolute_import
+
+import os
+import re
+import posixpath
+import traceback
+import pkg_resources
+from collections import namedtuple
+from docutils import nodes
+from sphinx import addnodes
+from sphinx.util.osutil import ensuredir
+
+import blockdiag.utils.rst.nodes
+import blockdiag.utils.rst.directives
+from blockdiag.utils.bootstrap import detectfont, Application
+from blockdiag.utils.compat import u, string_types
+from blockdiag.utils.fontmap import FontMap
+from blockdiag.utils.rst.directives import with_blockdiag
+
+# fontconfig; it will be initialized on `builder-inited` event.
+fontmap = None
+
+
+class blockdiag_node(blockdiag.utils.rst.nodes.blockdiag):
+    def to_drawer(self, image_format, builder, **kwargs):
+        if 'filename' in kwargs:
+            filename = kwargs.pop('filename')
+        else:
+            filename = self.get_abspath(image_format, builder)
+
+        antialias = builder.config.blockdiag_antialias
+        transparency = builder.config.blockdiag_transparency
+        image = super(blockdiag_node, self).to_drawer(image_format, filename, fontmap,
+                                                      antialias=antialias, transparency=transparency,
+                                                      **kwargs)
+        for node in image.diagram.traverse_nodes():
+            node.href = resolve_reference(builder, node.href)
+
+        return image
+
+    def get_relpath(self, image_format, builder):
+        options = dict(antialias=builder.config.blockdiag_antialias,
+                       fontpath=builder.config.blockdiag_fontpath,
+                       fontmap=builder.config.blockdiag_fontmap,
+                       format=image_format,
+                       transparency=builder.config.blockdiag_transparency)
+        if hasattr(builder, 'imgpath'):  # Sphinx (<= 1.2.x) or HTML writer
+            outputdir = builder.imgpath
+        else:
+            outputdir = ''
+        return posixpath.join(outputdir, self.get_path(**options))
+
+    def get_abspath(self, image_format, builder):
+        options = dict(antialias=builder.config.blockdiag_antialias,
+                       fontpath=builder.config.blockdiag_fontpath,
+                       fontmap=builder.config.blockdiag_fontmap,
+                       format=image_format,
+                       transparency=builder.config.blockdiag_transparency)
+
+        if hasattr(builder, 'imagedir'):  # Sphinx (>= 1.3.x)
+            outputdir = os.path.join(builder.outdir, builder.imagedir)
+        elif hasattr(builder, 'imgpath'):  # Sphinx (<= 1.2.x) and HTML writer
+            outputdir = os.path.join(builder.outdir, '_images')
+        else:
+            outputdir = builder.outdir
+        path = os.path.join(outputdir, self.get_path(**options))
+        ensuredir(os.path.dirname(path))
+
+        return path
+
+
+class Blockdiag(blockdiag.utils.rst.directives.BlockdiagDirective):
+    node_class = blockdiag_node
+
+    def node2image(self, node, diagram):
+        return node
+
+
+def resolve_reference(builder, href):
+    if href is None:
+        return None
+
+    pattern = re.compile(u("^:ref:`(.+?)`"), re.UNICODE)
+    matched = pattern.search(href)
+    if matched is None:
+        return href
+    elif not hasattr(builder, 'current_docname'):  # ex. latex builder
+        return matched.group(1)
+    else:
+        refid = matched.group(1)
+        domain = builder.env.domains['std']
+        node = addnodes.pending_xref(refexplicit=False)
+        xref = domain.resolve_xref(builder.env, builder.current_docname, builder,
+                                   'ref', refid, node, node)
+        if xref:
+            if 'refid' in xref:
+                return "#" + xref['refid']
+            else:
+                return xref['refuri']
+        else:
+            builder.warn('undefined label: %s' % refid)
+            return None
+
+
+def html_render_svg(self, node):
+    image = node.to_drawer('SVG', self.builder, filename=None, nodoctype=True)
+    image.draw()
+
+    if 'align' in node['options']:
+        align = node['options']['align']
+        self.body.append('<div align="%s" class="align-%s">' % (align, align))
+        self.context.append('</div>\n')
+    else:
+        self.body.append('<div>')
+        self.context.append('</div>\n')
+
+    # reftarget
+    for node_id in node['ids']:
+        self.body.append('<span id="%s"></span>' % node_id)
+
+    # resize image
+    size = image.pagesize().resize(**node['options'])
+    self.body.append(image.save(size))
+    self.context.append('')
+
+
+def html_render_clickablemap(self, image, width_ratio, height_ratio):
+    href_nodes = [node for node in image.nodes if node.href]
+    if not href_nodes:
+        return
+
+    self.body.append('<map name="map_%d">' % id(image))
+    for node in href_nodes:
+        x1, y1, x2, y2 = image.metrics.cell(node)
+
+        x1 *= width_ratio
+        x2 *= width_ratio
+        y1 *= height_ratio
+        y2 *= height_ratio
+        areatag = '<area shape="rect" coords="%s,%s,%s,%s" href="%s">' % (x1, y1, x2, y2, node.href)
+        self.body.append(areatag)
+
+    self.body.append('</map>')
+
+
+def html_render_png(self, node):
+    image = node.to_drawer('PNG', self.builder)
+    if not os.path.isfile(image.filename):
+        image.draw()
+        image.save()
+
+    # align
+    if 'align' in node['options']:
+        align = node['options']['align']
+        self.body.append('<div align="%s" class="align-%s">' % (align, align))
+        self.context.append('</div>\n')
+    else:
+        self.body.append('<div>')
+        self.context.append('</div>')
+
+    # link to original image
+    relpath = node.get_relpath('PNG', self.builder)
+    if 'width' in node['options'] or 'height' in node['options'] or 'scale' in node['options']:
+        self.body.append('<a class="reference internal image-reference" href="%s">' % relpath)
+        self.context.append('</a>')
+    else:
+        self.context.append('')
+
+    # <img> tag
+    original_size = image.pagesize()
+    resized = original_size.resize(**node['options'])
+    img_attr = dict(src=relpath,
+                    width=resized.width,
+                    height=resized.height)
+
+    if any(node.href for node in image.nodes):
+        img_attr['usemap'] = "#map_%d" % id(image)
+
+        width_ratio = float(resized.width) / original_size.width
+        height_ratio = float(resized.height) / original_size.height
+        html_render_clickablemap(self, image, width_ratio, height_ratio)
+
+    if 'alt' in node['options']:
+        img_attr['alt'] = node['options']['alt']
+
+    self.body.append(self.starttag(node, 'img', '', empty=True, **img_attr))
+
+
+@with_blockdiag
+def html_visit_blockdiag(self, node):
+    try:
+        image_format = get_image_format_for(self.builder)
+        if image_format.upper() == 'SVG':
+            html_render_svg(self, node)
+        else:
+            html_render_png(self, node)
+    except UnicodeEncodeError:
+        if self.builder.config.blockdiag_debug:
+            traceback.print_exc()
+
+        msg = ("blockdiag error: UnicodeEncodeError caught "
+               "(check your font settings)")
+        self.builder.warn(msg)
+        raise nodes.SkipNode
+    except Exception as exc:
+        if self.builder.config.blockdiag_debug:
+            traceback.print_exc()
+
+        self.builder.warn('dot code %r: %s' % (node['code'], str(exc)))
+        raise nodes.SkipNode
+
+
+def html_depart_blockdiag(self, node):
+    self.body.append(self.context.pop())
+    self.body.append(self.context.pop())
+
+
+def get_image_format_for(builder):
+    if builder.format in ('html', 'slides'):
+        image_format = builder.config.blockdiag_html_image_format.upper()
+    elif builder.format == 'latex':
+        if builder.config.blockdiag_tex_image_format:
+            image_format = builder.config.blockdiag_tex_image_format.upper()
+        else:
+            image_format = builder.config.blockdiag_latex_image_format.upper()
+    else:
+        image_format = 'PNG'
+
+    if image_format.upper() not in ('PNG', 'PDF', 'SVG'):
+        raise ValueError('unknown format: %s' % image_format)
+
+    if image_format.upper() == 'PDF':
+        try:
+            import reportlab  # NOQA: importing test
+        except ImportError:
+            raise ImportError('Could not output PDF format. Install reportlab.')
+
+    return image_format
+
+
+def on_builder_inited(self):
+    # show deprecated message
+    if self.builder.config.blockdiag_tex_image_format:
+        self.builder.warn('blockdiag_tex_image_format is deprecated. Use blockdiag_latex_image_format.')
+
+    # initialize fontmap
+    global fontmap
+
+    try:
+        fontmappath = self.builder.config.blockdiag_fontmap
+        fontmap = FontMap(fontmappath)
+    except:
+        fontmap = FontMap(None)
+
+    try:
+        fontpath = self.builder.config.blockdiag_fontpath
+        if isinstance(fontpath, string_types):
+            fontpath = [fontpath]
+
+        if fontpath:
+            config = namedtuple('Config', 'font')(fontpath)
+            fontpath = detectfont(config)
+            fontmap.set_default_font(fontpath)
+    except:
+        pass
+
+
+def on_doctree_resolved(self, doctree, docname):
+    if self.builder.format in ('html', 'slides'):
+        return
+
+    try:
+        image_format = get_image_format_for(self.builder)
+    except Exception as exc:
+        if self.builder.config.blockdiag_debug:
+            traceback.print_exc()
+
+        self.builder.warn('blockdiag error: %s' % exc)
+        for node in doctree.traverse(blockdiag_node):
+            node.parent.remove(node)
+
+        return
+
+    for node in doctree.traverse(blockdiag_node):
+        try:
+            with Application():
+                relfn = node.get_relpath(image_format, self.builder)
+                image = node.to_drawer(image_format, self.builder)
+                if not os.path.isfile(image.filename):
+                    image.draw()
+                    image.save()
+
+                image = nodes.image(uri=relfn, candidates={'*': relfn}, **node['options'])
+                node.parent.replace(node, image)
+        except Exception as exc:
+            if self.builder.config.blockdiag_debug:
+                traceback.print_exc()
+
+            self.builder.warn('dot code %r: %s' % (node['code'], str(exc)))
+            node.parent.remove(node)
+
+
+def setup(app):
+    app.add_node(blockdiag_node,
+                 html=(html_visit_blockdiag, html_depart_blockdiag))
+    app.add_directive('blockdiag', Blockdiag)
+    app.add_config_value('blockdiag_fontpath', None, 'html')
+    app.add_config_value('blockdiag_fontmap', None, 'html')
+    app.add_config_value('blockdiag_antialias', False, 'html')
+    app.add_config_value('blockdiag_transparency', True, 'html')
+    app.add_config_value('blockdiag_debug', False, 'html')
+    app.add_config_value('blockdiag_html_image_format', 'PNG', 'html')
+    app.add_config_value('blockdiag_tex_image_format', None, 'html')  # backward compatibility for 1.3.1
+    app.add_config_value('blockdiag_latex_image_format', 'PNG', 'html')
+    app.connect("builder-inited", on_builder_inited)
+    app.connect("doctree-resolved", on_doctree_resolved)
+
+    return {
+        'version': pkg_resources.require('blockdiag')[0].version,
+        'parallel_read_safe': True,
+        'parallel_write_safe': True,
+    }