[blockdiag] 12/29: Import Upstream version 1.2.4

Andreas Tille tille at debian.org
Tue Jan 10 21:35:58 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository blockdiag.

commit 12803a9822ec460a140c98d3ab356f1caa1351bc
Author: Andreas Tille <tille at debian.org>
Date:   Tue Jan 10 11:08:03 2017 +0100

    Import Upstream version 1.2.4
---
 PKG-INFO                                           |  41 ++-
 bootstrap.py                                       |  10 +-
 buildout.cfg                                       |  15 +-
 setup.py                                           |   8 +-
 src/README.txt                                     |  36 +++
 src/blockdiag.egg-info/PKG-INFO                    |  41 ++-
 src/blockdiag.egg-info/SOURCES.txt                 |  17 +-
 src/blockdiag.egg-info/entry_points.txt            |   5 +
 src/blockdiag.egg-info/requires.txt                |   3 +-
 src/blockdiag/DiagramMetrics.py                    |  16 -
 src/blockdiag/__init__.py                          |   2 +-
 src/blockdiag/builder.py                           |  40 +--
 src/blockdiag/command.py                           |   7 +-
 src/blockdiag/drawer.py                            |  84 ++---
 src/blockdiag/elements.py                          | 144 ++++-----
 src/blockdiag/imagedraw/__init__.py                |  48 +--
 src/blockdiag/imagedraw/base.py                    |  79 +++++
 src/blockdiag/imagedraw/filters/linejump.py        |  27 +-
 src/blockdiag/imagedraw/pdf.py                     |  57 ++--
 src/blockdiag/imagedraw/png.py                     | 243 +++++++++-----
 src/blockdiag/imagedraw/simplesvg.py               |  37 ++-
 src/blockdiag/imagedraw/svg.py                     | 350 +++++++++++----------
 src/blockdiag/imagedraw/textfolder.py              | 298 ++++++++++++++++++
 src/blockdiag/imagedraw/utils/__init__.py          | 115 +++++++
 src/blockdiag/{ => imagedraw}/utils/ellipse.py     |  29 +-
 src/blockdiag/metrics.py                           | 322 ++++++++++---------
 src/blockdiag/noderenderer/__init__.py             |  23 +-
 src/blockdiag/noderenderer/actor.py                |  14 +-
 src/blockdiag/noderenderer/beginpoint.py           |   8 +-
 src/blockdiag/noderenderer/box.py                  |  11 +-
 src/blockdiag/noderenderer/circle.py               |  10 +-
 src/blockdiag/noderenderer/cloud.py                |  31 +-
 src/blockdiag/noderenderer/diamond.py              |  11 +-
 src/blockdiag/noderenderer/dots.py                 |   6 +-
 src/blockdiag/noderenderer/ellipse.py              |  11 +-
 src/blockdiag/noderenderer/endpoint.py             |   8 +-
 src/blockdiag/noderenderer/flowchart/database.py   |  40 ++-
 src/blockdiag/noderenderer/flowchart/input.py      |  13 +-
 src/blockdiag/noderenderer/flowchart/loopin.py     |  11 +-
 src/blockdiag/noderenderer/flowchart/loopout.py    |  25 +-
 src/blockdiag/noderenderer/flowchart/terminator.py |  31 +-
 src/blockdiag/noderenderer/mail.py                 |  11 +-
 src/blockdiag/noderenderer/minidiamond.py          |   9 +-
 src/blockdiag/noderenderer/none.py                 |   2 +-
 src/blockdiag/noderenderer/note.py                 |  11 +-
 src/blockdiag/noderenderer/roundedbox.py           |  44 ++-
 src/blockdiag/noderenderer/square.py               |  12 +-
 src/blockdiag/noderenderer/textbox.py              |   4 +-
 src/blockdiag/parser.py                            |  66 ++--
 .../tests/diagrams/diagram_attributes.diag         |   1 +
 src/blockdiag/tests/diagrams/node_attribute.diag   |   1 +
 .../diagrams/node_shape_with_none_shadow.diag      |  31 ++
 .../diagrams/node_shape_with_solid_shadow.diag     |  31 ++
 src/blockdiag/tests/test_boot_params.py            |  77 ++++-
 src/blockdiag/tests/test_builder.py                |   2 +-
 src/blockdiag/tests/test_builder_edge.py           |   8 +-
 src/blockdiag/tests/test_builder_errors.py         |  43 +--
 src/blockdiag/tests/test_builder_group.py          |   5 +-
 src/blockdiag/tests/test_builder_node.py           |  16 +-
 src/blockdiag/tests/test_builder_separate.py       |   9 +-
 src/blockdiag/tests/test_generate_diagram.py       |  53 ++--
 src/blockdiag/tests/test_imagedraw_textfolder.py   |  97 ++++++
 src/blockdiag/tests/test_parser.py                 | 141 ++++-----
 src/blockdiag/tests/test_pep8.py                   |  62 ++--
 src/blockdiag/tests/test_rst_directives.py         | 148 ++++++++-
 src/blockdiag/tests/test_utils_fontmap.py          |   9 +-
 src/blockdiag/tests/utils.py                       |  45 ++-
 src/blockdiag/utils/PDFTextFolder.py               |  29 --
 src/blockdiag/utils/PILTextFolder.py               |  54 ----
 src/blockdiag/utils/TextFolder.py                  | 296 -----------------
 src/blockdiag/utils/__init__.py                    |  65 +++-
 src/blockdiag/utils/bootstrap.py                   | 103 ++++--
 src/blockdiag/utils/config.py                      |   1 -
 src/blockdiag/utils/fontmap.py                     |   7 +-
 src/blockdiag/utils/functools.py                   |  39 +++
 src/blockdiag/utils/images.py                      |   5 +-
 src/blockdiag/utils/jpeg.py                        |   4 +-
 src/blockdiag/utils/myitertools.py                 |   6 +-
 src/blockdiag/utils/namedtuple.py                  |   3 +-
 src/blockdiag/utils/rst/directives.py              |  83 +++--
 .../{DiagramDraw.py => utils/rst/nodes.py}         |   6 +-
 src/blockdiag/utils/uuid.py                        |   6 +-
 src/blockdiag_sphinxhelper.py                      |  19 +-
 83 files changed, 2551 insertions(+), 1440 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 9233748..74aa02e 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,11 +1,12 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: blockdiag
-Version: 1.1.6
+Version: 1.2.4
 Summary: blockdiag generate block-diagram image file from spec-text file.
 Home-page: http://blockdiag.com/
 Author: Takeshi Komiya
 Author-email: i.tkomiya at gmail.com
 License: Apache License 2.0
+Download-URL: http://pypi.python.org/pypi/blockdiag
 Description: `blockdiag` generate block-diagram image file from spec-text file.
         
         Features
@@ -121,6 +122,42 @@ Description: `blockdiag` generate block-diagram image file from spec-text file.
         History
         =======
         
+        1.2.4 (2012-11-21)
+        ------------------
+        * Fix bugs
+        
+        1.2.3 (2012-11-05)
+        ------------------
+        * Fix bugs
+        
+        1.2.2 (2012-10-28)
+        ------------------
+        * Fix bugs
+        
+        1.2.1 (2012-10-28)
+        ------------------
+        * Add external imagedraw plugin supports
+        * Add node attribute: label_orientation*
+        * Fix bugs
+        
+        1.2.0 (2012-10-22)
+        ------------------
+        * Optimize algorithm for rendering shadow
+        * Add options to docutils directive
+        * Fix bugs
+        
+        1.1.8 (2012-09-28)
+        ------------------
+        * Add --ignore-pil option
+        * Fix bugs
+        
+        1.1.7 (2012-09-20)
+        ------------------
+        * Add diagram attribute: shadow_style
+        * Add font path for centos 6.2
+        * Add a setting 'antialias' in the configuration file
+        * Fix bugs
+        
         1.1.6 (2012-06-06)
         ------------------
         * Support for readthedocs.org
diff --git a/bootstrap.py b/bootstrap.py
index 7647cbb..716795f 100644
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -134,7 +134,9 @@ parser.add_option("-c", None, action="store", dest="config_file",
                    help=("Specify the path to the buildout configuration "
                          "file to be used."))
 
-options, args = parser.parse_args()
+options, orig_args = parser.parse_args()
+
+args = []
 
 # if -c was provided, we push it back into args for buildout's main function
 if options.config_file is not None:
@@ -153,7 +155,6 @@ if options.setup_source is None:
 
 if options.accept_buildout_test_releases:
     args.append('buildout:accept-buildout-test-releases=true')
-args.append('bootstrap')
 
 try:
     import pkg_resources
@@ -257,6 +258,9 @@ if exitcode != 0:
 ws.add_entry(eggs_dir)
 ws.require(requirement)
 import zc.buildout.buildout
-zc.buildout.buildout.main(args)
+if orig_args:
+    # run buildout with commands passed to bootstrap.py, then actually bootstrap
+    zc.buildout.buildout.main(args + orig_args)
+zc.buildout.buildout.main(args + ['bootstrap'])
 if not options.eggs:  # clean up temporary egg directory
     shutil.rmtree(eggs_dir)
diff --git a/buildout.cfg b/buildout.cfg
index f7a8df2..cb44b9f 100644
--- a/buildout.cfg
+++ b/buildout.cfg
@@ -1,5 +1,5 @@
 [buildout]
-parts = blockdiag test coverage
+parts = blockdiag test coverage pyflakes pylint
 
 develop = .
 
@@ -20,6 +20,19 @@ eggs =
 recipe = zc.recipe.egg
 eggs = coverage
 
+[pyflakes]
+recipe = zc.recipe.egg
+eggs = pyflakes
+scripts = pyflakes
+entry-points = pyflakes=pyflakes.scripts.pyflakes:main
+
+[pylint]
+recipe = zc.recipe.egg
+eggs =
+    pylint
+    blockdiag[test]
+scripts = pylint
+
 [test-extra]
 recipe = iw.recipe.cmd:py
 on_install = true
diff --git a/setup.py b/setup.py
index 6528889..1f72f01 100644
--- a/setup.py
+++ b/setup.py
@@ -65,6 +65,7 @@ setup(
      author='Takeshi Komiya',
      author_email='i.tkomiya at gmail.com',
      url='http://blockdiag.com/',
+     download_url='http://pypi.python.org/pypi/blockdiag',
      license='Apache License 2.0',
      py_modules=['blockdiag_sphinxhelper'],
      packages=find_packages('src'),
@@ -75,7 +76,7 @@ setup(
      extras_require=dict(
          test=[
              'Nose',
-             'pep8',
+             'pep8>=1.3',
              'unittest2',
          ],
          pdf=[
@@ -118,6 +119,11 @@ setup(
         [blockdiag_plugins]
         attributes = blockdiag.plugins.attributes
         autoclass = blockdiag.plugins.autoclass
+
+        [blockdiag_imagedrawers]
+        imagedraw_png = blockdiag.imagedraw.png
+        imagedraw_svg = blockdiag.imagedraw.svg
+        imagedraw_pdf = blockdiag.imagedraw.pdf
      """,
 )
 
diff --git a/src/README.txt b/src/README.txt
index f9a6877..132832d 100644
--- a/src/README.txt
+++ b/src/README.txt
@@ -113,6 +113,42 @@ Apache License 2.0
 History
 =======
 
+1.2.4 (2012-11-21)
+------------------
+* Fix bugs
+
+1.2.3 (2012-11-05)
+------------------
+* Fix bugs
+
+1.2.2 (2012-10-28)
+------------------
+* Fix bugs
+
+1.2.1 (2012-10-28)
+------------------
+* Add external imagedraw plugin supports
+* Add node attribute: label_orientation*
+* Fix bugs
+
+1.2.0 (2012-10-22)
+------------------
+* Optimize algorithm for rendering shadow
+* Add options to docutils directive
+* Fix bugs
+
+1.1.8 (2012-09-28)
+------------------
+* Add --ignore-pil option
+* Fix bugs
+
+1.1.7 (2012-09-20)
+------------------
+* Add diagram attribute: shadow_style
+* Add font path for centos 6.2
+* Add a setting 'antialias' in the configuration file
+* Fix bugs
+
 1.1.6 (2012-06-06)
 ------------------
 * Support for readthedocs.org
diff --git a/src/blockdiag.egg-info/PKG-INFO b/src/blockdiag.egg-info/PKG-INFO
index 9233748..74aa02e 100644
--- a/src/blockdiag.egg-info/PKG-INFO
+++ b/src/blockdiag.egg-info/PKG-INFO
@@ -1,11 +1,12 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: blockdiag
-Version: 1.1.6
+Version: 1.2.4
 Summary: blockdiag generate block-diagram image file from spec-text file.
 Home-page: http://blockdiag.com/
 Author: Takeshi Komiya
 Author-email: i.tkomiya at gmail.com
 License: Apache License 2.0
+Download-URL: http://pypi.python.org/pypi/blockdiag
 Description: `blockdiag` generate block-diagram image file from spec-text file.
         
         Features
@@ -121,6 +122,42 @@ Description: `blockdiag` generate block-diagram image file from spec-text file.
         History
         =======
         
+        1.2.4 (2012-11-21)
+        ------------------
+        * Fix bugs
+        
+        1.2.3 (2012-11-05)
+        ------------------
+        * Fix bugs
+        
+        1.2.2 (2012-10-28)
+        ------------------
+        * Fix bugs
+        
+        1.2.1 (2012-10-28)
+        ------------------
+        * Add external imagedraw plugin supports
+        * Add node attribute: label_orientation*
+        * Fix bugs
+        
+        1.2.0 (2012-10-22)
+        ------------------
+        * Optimize algorithm for rendering shadow
+        * Add options to docutils directive
+        * Fix bugs
+        
+        1.1.8 (2012-09-28)
+        ------------------
+        * Add --ignore-pil option
+        * Fix bugs
+        
+        1.1.7 (2012-09-20)
+        ------------------
+        * Add diagram attribute: shadow_style
+        * Add font path for centos 6.2
+        * Add a setting 'antialias' in the configuration file
+        * Fix bugs
+        
         1.1.6 (2012-06-06)
         ------------------
         * Support for readthedocs.org
diff --git a/src/blockdiag.egg-info/SOURCES.txt b/src/blockdiag.egg-info/SOURCES.txt
index 77aa2f1..2fe42fc 100644
--- a/src/blockdiag.egg-info/SOURCES.txt
+++ b/src/blockdiag.egg-info/SOURCES.txt
@@ -23,8 +23,6 @@ examples/simple.svg
 src/README.txt
 src/TODO.txt
 src/blockdiag_sphinxhelper.py
-src/blockdiag/DiagramDraw.py
-src/blockdiag/DiagramMetrics.py
 src/blockdiag/__init__.py
 src/blockdiag/builder.py
 src/blockdiag/command.py
@@ -39,12 +37,16 @@ src/blockdiag.egg-info/entry_points.txt
 src/blockdiag.egg-info/requires.txt
 src/blockdiag.egg-info/top_level.txt
 src/blockdiag/imagedraw/__init__.py
+src/blockdiag/imagedraw/base.py
 src/blockdiag/imagedraw/pdf.py
 src/blockdiag/imagedraw/png.py
 src/blockdiag/imagedraw/simplesvg.py
 src/blockdiag/imagedraw/svg.py
+src/blockdiag/imagedraw/textfolder.py
 src/blockdiag/imagedraw/filters/__init__.py
 src/blockdiag/imagedraw/filters/linejump.py
+src/blockdiag/imagedraw/utils/__init__.py
+src/blockdiag/imagedraw/utils/ellipse.py
 src/blockdiag/noderenderer/__init__.py
 src/blockdiag/noderenderer/actor.py
 src/blockdiag/noderenderer/beginpoint.py
@@ -80,6 +82,7 @@ src/blockdiag/tests/test_builder_group.py
 src/blockdiag/tests/test_builder_node.py
 src/blockdiag/tests/test_builder_separate.py
 src/blockdiag/tests/test_generate_diagram.py
+src/blockdiag/tests/test_imagedraw_textfolder.py
 src/blockdiag/tests/test_parser.py
 src/blockdiag/tests/test_pep8.py
 src/blockdiag/tests/test_rst_directives.py
@@ -156,6 +159,8 @@ src/blockdiag/tests/diagrams/node_rotated_labels.diag
 src/blockdiag/tests/diagrams/node_shape.diag
 src/blockdiag/tests/diagrams/node_shape_background.diag
 src/blockdiag/tests/diagrams/node_shape_namespace.diag
+src/blockdiag/tests/diagrams/node_shape_with_none_shadow.diag
+src/blockdiag/tests/diagrams/node_shape_with_solid_shadow.diag
 src/blockdiag/tests/diagrams/node_style_dasharray.diag
 src/blockdiag/tests/diagrams/node_styles.diag
 src/blockdiag/tests/diagrams/node_width_and_height.diag
@@ -217,15 +222,12 @@ src/blockdiag/tests/diagrams/errors/unknown_node_class.diag
 src/blockdiag/tests/diagrams/errors/unknown_node_shape.diag
 src/blockdiag/tests/diagrams/errors/unknown_node_style.diag
 src/blockdiag/tests/diagrams/errors/unknown_plugin.diag
-src/blockdiag/utils/PDFTextFolder.py
-src/blockdiag/utils/PILTextFolder.py
-src/blockdiag/utils/TextFolder.py
 src/blockdiag/utils/__init__.py
 src/blockdiag/utils/bootstrap.py
 src/blockdiag/utils/collections.py
 src/blockdiag/utils/config.py
-src/blockdiag/utils/ellipse.py
 src/blockdiag/utils/fontmap.py
+src/blockdiag/utils/functools.py
 src/blockdiag/utils/images.py
 src/blockdiag/utils/jpeg.py
 src/blockdiag/utils/myitertools.py
@@ -233,4 +235,5 @@ src/blockdiag/utils/namedtuple.py
 src/blockdiag/utils/urlutil.py
 src/blockdiag/utils/uuid.py
 src/blockdiag/utils/rst/__init__.py
-src/blockdiag/utils/rst/directives.py
\ No newline at end of file
+src/blockdiag/utils/rst/directives.py
+src/blockdiag/utils/rst/nodes.py
\ No newline at end of file
diff --git a/src/blockdiag.egg-info/entry_points.txt b/src/blockdiag.egg-info/entry_points.txt
index b146167..07a8580 100644
--- a/src/blockdiag.egg-info/entry_points.txt
+++ b/src/blockdiag.egg-info/entry_points.txt
@@ -28,4 +28,9 @@
         [blockdiag_plugins]
         attributes = blockdiag.plugins.attributes
         autoclass = blockdiag.plugins.autoclass
+
+        [blockdiag_imagedrawers]
+        imagedraw_png = blockdiag.imagedraw.png
+        imagedraw_svg = blockdiag.imagedraw.svg
+        imagedraw_pdf = blockdiag.imagedraw.pdf
      
\ No newline at end of file
diff --git a/src/blockdiag.egg-info/requires.txt b/src/blockdiag.egg-info/requires.txt
index b311d36..3dc7b6e 100644
--- a/src/blockdiag.egg-info/requires.txt
+++ b/src/blockdiag.egg-info/requires.txt
@@ -2,7 +2,6 @@ setuptools
 funcparserlib
 webcolors
 PIL
-OrderedDict
 
 [rst]
 docutils
@@ -12,5 +11,5 @@ reportlab
 
 [test]
 Nose
-pep8
+pep8>=1.3
 unittest2
\ No newline at end of file
diff --git a/src/blockdiag/DiagramMetrics.py b/src/blockdiag/DiagramMetrics.py
deleted file mode 100644
index e05463c..0000000
--- a/src/blockdiag/DiagramMetrics.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-#  Copyright 2011 Takeshi KOMIYA
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-from blockdiag.metrics import *
diff --git a/src/blockdiag/__init__.py b/src/blockdiag/__init__.py
index 1366786..4343b09 100644
--- a/src/blockdiag/__init__.py
+++ b/src/blockdiag/__init__.py
@@ -13,4 +13,4 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-__version__ = '1.1.6'
+__version__ = '1.2.4'
diff --git a/src/blockdiag/builder.py b/src/blockdiag/builder.py
index 4639319..f632e15 100644
--- a/src/blockdiag/builder.py
+++ b/src/blockdiag/builder.py
@@ -13,9 +13,9 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-from elements import *
-import parser
-from utils import XY
+from blockdiag import parser
+from blockdiag.elements import Diagram, DiagramNode, NodeGroup, DiagramEdge
+from blockdiag.utils import unquote, XY
 
 
 class DiagramTreeBuilder:
@@ -366,8 +366,8 @@ class DiagramLayoutManager:
 
             return cmp(x.node1.order, y.node1.order)
 
-        edges = DiagramEdge.find(parent, node1) + \
-                DiagramEdge.find(parent, node2)
+        edges = (DiagramEdge.find(parent, node1) +
+                 DiagramEdge.find(parent, node2))
         edges.sort(compare)
         if len(edges) == 0:
             return 0
@@ -411,8 +411,8 @@ class DiagramLayoutManager:
                     if parent_height and parent_height > height:
                         height = parent_height
 
-                if prev_child and grandchild > 1 and \
-                   not self.is_rhombus(prev_child, child):
+                if (prev_child and grandchild > 1 and
+                   (not self.is_rhombus(prev_child, child))):
                     coord = [p.y for p in self.coordinates if p.x > child.xy.x]
                     if coord and max(coord) >= node.xy.y:
                         height = max(coord) + 1
@@ -502,33 +502,33 @@ class EdgeLayoutManager(object):
 
     @property
     def edges(self):
-        for edge in (e for e in self.diagram.edges  if e.style != 'none'):
+        for edge in (e for e in self.diagram.edges if e.style != 'none'):
             yield edge
 
         for group in self.groups:
-            for edge in (e for e in group.edges  if e.style != 'none'):
+            for edge in (e for e in group.edges if e.style != 'none'):
                 yield edge
 
     def run(self):
         for edge in self.edges:
-            dir = edge.direction
+            _dir = edge.direction
 
             if edge.node1.group.orientation == 'landscape':
-                if dir == 'right':
+                if _dir == 'right':
                     r = range(edge.node1.xy.x + 1, edge.node2.xy.x)
                     for x in r:
                         xy = (x, edge.node1.xy.y)
                         nodes = [x for x in self.nodes if x.xy == xy]
                         if len(nodes) > 0:
                             edge.skipped = 1
-                elif dir == 'right-up':
+                elif _dir == 'right-up':
                     r = range(edge.node1.xy.x + 1, edge.node2.xy.x)
                     for x in r:
                         xy = (x, edge.node1.xy.y)
                         nodes = [x for x in self.nodes if x.xy == xy]
                         if len(nodes) > 0:
                             edge.skipped = 1
-                elif dir == 'right-down':
+                elif _dir == 'right-down':
                     if self.diagram.edge_layout == 'flowchart':
                         r = range(edge.node1.xy.y, edge.node2.xy.y)
                         for y in r:
@@ -543,14 +543,14 @@ class EdgeLayoutManager(object):
                         nodes = [x for x in self.nodes if x.xy == xy]
                         if len(nodes) > 0:
                             edge.skipped = 1
-                elif dir in ('left-down', 'down'):
+                elif _dir in ('left-down', 'down'):
                     r = range(edge.node1.xy.y + 1, edge.node2.xy.y)
                     for y in r:
                         xy = (edge.node1.xy.x, y)
                         nodes = [x for x in self.nodes if x.xy == xy]
                         if len(nodes) > 0:
                             edge.skipped = 1
-                elif dir == 'up':
+                elif _dir == 'up':
                     r = range(edge.node2.xy.y + 1, edge.node1.xy.y)
                     for y in r:
                         xy = (edge.node1.xy.x, y)
@@ -558,21 +558,21 @@ class EdgeLayoutManager(object):
                         if len(nodes) > 0:
                             edge.skipped = 1
             else:
-                if dir == 'right':
+                if _dir == 'right':
                     r = range(edge.node1.xy.x + 1, edge.node2.xy.x)
                     for x in r:
                         xy = (x, edge.node1.xy.y)
                         nodes = [x for x in self.nodes if x.xy == xy]
                         if len(nodes) > 0:
                             edge.skipped = 1
-                elif dir in ('left-down', 'down'):
+                elif _dir in ('left-down', 'down'):
                     r = range(edge.node1.xy.y + 1, edge.node2.xy.y)
                     for y in r:
                         xy = (edge.node1.xy.x, y)
                         nodes = [x for x in self.nodes if x.xy == xy]
                         if len(nodes) > 0:
                             edge.skipped = 1
-                elif dir == 'right-down':
+                elif _dir == 'right-down':
                     if self.diagram.edge_layout == 'flowchart':
                         r = range(edge.node1.xy.x, edge.node2.xy.x)
                         for x in r:
@@ -687,8 +687,8 @@ class SeparateDiagramBuilder(ScreenNodeBuilder):
             base.level = group.level - 1
 
             # bind edges on base diagram (outer the group)
-            edges = DiagramEdge.find(None, group) + \
-                    DiagramEdge.find(group, None)
+            edges = (DiagramEdge.find(None, group) +
+                     DiagramEdge.find(group, None))
             base.edges = self._filter_edges(edges, self.diagram, group.level)
 
             # bind edges on target group (inner the group)
diff --git a/src/blockdiag/command.py b/src/blockdiag/command.py
index 3d67a80..6563fdb 100644
--- a/src/blockdiag/command.py
+++ b/src/blockdiag/command.py
@@ -14,15 +14,16 @@
 #  limitations under the License.
 
 import re
-import sys
 import blockdiag
 import blockdiag.builder
 import blockdiag.drawer
 import blockdiag.parser
 from blockdiag.utils.bootstrap import Application, Options
 
-# for compatibility
-from blockdiag.utils.bootstrap import create_fontmap, detectfont
+
+# FIXME: for compatibility
+from blockdiag.utils.bootstrap import detectfont
+detectfont
 
 
 class BlockdiagOptions(Options):
diff --git a/src/blockdiag/drawer.py b/src/blockdiag/drawer.py
index 69e12de..1a62aab 100644
--- a/src/blockdiag/drawer.py
+++ b/src/blockdiag/drawer.py
@@ -13,52 +13,49 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-import noderenderer
-import imagedraw
-from metrics import AutoScaler
-from metrics import DiagramMetrics
-from imagedraw.filters.linejump import LineJumpDrawFilter
+from blockdiag import imagedraw, noderenderer
+from blockdiag.metrics import AutoScaler, DiagramMetrics
+from blockdiag.utils.collections import defaultdict
 
 
 class DiagramDraw(object):
-    MetricsClass = DiagramMetrics
+    MetricsClass = None  # TODO: obsoleted interface
+    shadow_colors = defaultdict(lambda: (0, 0, 0))
+    shadow_colors['PNG'] = (64, 64, 64)
+    shadow_colors['PDF'] = (144, 144, 144)
 
-    @classmethod
-    def set_metrics_class(cls, MetricsClass):
-        cls.MetricsClass = MetricsClass
-
-    def __init__(self, format, diagram, filename=None, **kwargs):
-        self.format = format.upper()
+    def __init__(self, _format, diagram, filename=None, **kwargs):
+        self.format = _format.upper()
         self.diagram = diagram
         self.fill = kwargs.get('fill', (0, 0, 0))
         self.badgeFill = kwargs.get('badgeFill', 'pink')
         self.filename = filename
-        self.scale_ratio = 1
-
-        basediagram = kwargs.get('basediagram', diagram)
-        self.metrics = self.MetricsClass(basediagram, **kwargs)
-
-        if self.format == 'PNG':
-            if kwargs.get('antialias'):
-                self.scale_ratio = ratio = 2
-                self.metrics = AutoScaler(self.metrics, scale_ratio=ratio)
-            self.shadow = kwargs.get('shadow', (64, 64, 64))
-        elif self.format == 'PDF':
-            self.shadow = kwargs.get('shadow', (144, 144, 144))
+        self.shadow = self.shadow_colors[self.format.upper()]
+
+        if self.format == 'PNG' and kwargs.get('antialias'):
+            self.scale_ratio = 2
         else:
-            self.shadow = kwargs.get('shadow', (0, 0, 0))
+            self.scale_ratio = 1
+
+        self.drawer = imagedraw.create(self.format, self.filename,
+                                       filters=['linejump'],
+                                       scale_ratio=self.scale_ratio,
+                                       **kwargs)
 
-        kwargs = dict(code=kwargs.get('code'),
-                      nodoctype=kwargs.get('nodoctype'),
-                      scale_ratio=self.scale_ratio,
-                      transparency=kwargs.get('transparency'))
-        drawer = imagedraw.create(self.format, self.filename,
-                                  self.pagesize(), **kwargs)
-        if drawer is None:
-            msg = 'failed to load %s image driver' % self.format
-            raise RuntimeError(msg)
+        self.metrics = self.create_metrics(kwargs.get('basediagram', diagram),
+                                           drawer=self.drawer, **kwargs)
+        if self.scale_ratio == 2:
+            self.metrics = AutoScaler(self.metrics,
+                                      scale_ratio=self.scale_ratio)
 
-        self.drawer = LineJumpDrawFilter(drawer, self.metrics.cellsize / 2)
+        self.drawer.set_canvas_size(self.pagesize())
+        self.drawer.set_options(jump_radius=self.metrics.cellsize / 2)
+
+    def create_metrics(self, *args, **kwargs):
+        if self.MetricsClass:
+            return self.MetricsClass(*args, **kwargs)
+        else:
+            return DiagramMetrics(*args, **kwargs)
 
     @property
     def nodes(self):
@@ -75,7 +72,7 @@ class DiagramDraw(object):
     @property
     def edges(self):
         edges = self.diagram.traverse_edges(preorder=True)
-        for edge in (e for e in edges  if e.style != 'none'):
+        for edge in (e for e in edges if e.style != 'none'):
             yield edge
 
     def pagesize(self, scaled=False):
@@ -94,10 +91,6 @@ class DiagramDraw(object):
         self._draw_background()
         self.metrics = temp
 
-        # Smoothing background images.
-        if self.format == 'PNG':
-            self.drawer.smoothCanvas()
-
         if self.scale_ratio > 1:
             pagesize = self.pagesize(scaled=True)
             self.drawer.resizeCanvas(pagesize)
@@ -118,7 +111,7 @@ class DiagramDraw(object):
 
         # Drop node shadows.
         for node in self.nodes:
-            if node.color != 'none':
+            if node.color != 'none' and self.diagram.shadow_style != 'none':
                 r = noderenderer.get(node.shape)
 
                 shape = r(node, self.metrics)
@@ -128,7 +121,8 @@ class DiagramDraw(object):
                     drawer = self.drawer
 
                 shape.render(drawer, self.format,
-                             fill=self.shadow, shadow=True)
+                             fill=self.shadow, shadow=True,
+                             style=self.diagram.shadow_style)
 
     def _draw_elements(self, **kwargs):
         for node in self.nodes:
@@ -137,6 +131,9 @@ class DiagramDraw(object):
         for edge in self.edges:
             self.edge(edge)
 
+        for edge in self.edges:
+            self.edge_label(edge)
+
         for group in self.groups:
             if group.shape == 'line':
                 box = self.metrics.group(group).marginbox
@@ -181,7 +178,10 @@ class DiagramDraw(object):
             else:
                 self.drawer.polygon(head, outline=edge.color, fill=edge.color)
 
+    def edge_label(self, edge):
         if edge.label:
+            metrics = self.metrics.edge(edge)
+
             font = self.metrics.font_for(edge)
             self.drawer.textarea(metrics.labelbox, edge.label, font=font,
                                  fill=edge.textcolor, outline=self.fill)
diff --git a/src/blockdiag/elements.py b/src/blockdiag/elements.py
index 22c424e..f46919f 100644
--- a/src/blockdiag/elements.py
+++ b/src/blockdiag/elements.py
@@ -17,32 +17,8 @@ import os
 import re
 import sys
 import copy
-from utils import images, urlutil, uuid, XY
-import plugins
-import noderenderer
-
-
-def unquote(string):
-    """
-    Remove quotas from string
-
-    >>> unquote('"test"')
-    'test'
-    >>> unquote("'test'")
-    'test'
-    >>> unquote("'half quoted")
-    "'half quoted"
-    >>> unquote('"half quoted')
-    '"half quoted'
-    """
-    if string:
-        m = re.match('\A(?P<quote>"|\')((.|\s)*)(?P=quote)\Z', string, re.M)
-        if m:
-            return re.sub("\\\\" + m.group(1), m.group(1), m.group(2))
-        else:
-            return string
-    else:
-        return string
+from blockdiag.utils import images, unquote, urlutil, uuid, XY
+from blockdiag import noderenderer, plugins
 
 
 class Base(object):
@@ -119,13 +95,13 @@ class Element(Base):
     int_attrs = Base.int_attrs + ['width', 'height']
 
     @classmethod
-    def get(cls, id):
-        if not id:
-            id = uuid.generate()
+    def get(cls, elemid):
+        if not elemid:
+            elemid = uuid.generate()
 
-        unquote_id = unquote(id)
+        unquote_id = unquote(elemid)
         if unquote_id not in cls.namespace:
-            obj = cls(id)
+            obj = cls(elemid)
             cls.namespace[unquote_id] = obj
 
         return cls.namespace[unquote_id]
@@ -137,8 +113,8 @@ class Element(Base):
         cls.basecolor = (255, 255, 255)
         cls.textcolor = (0, 0, 0)
 
-    def __init__(self, id):
-        self.id = unquote(id)
+    def __init__(self, elemid):
+        self.id = unquote(elemid)
         self.label = ''
         self.xy = XY(0, 0)
         self.group = None
@@ -152,16 +128,10 @@ class Element(Base):
         self.stacked = False
 
     def __repr__(self):
-        class_name = self.__class__.__name__
-        nodeid = self.id
-        xy = str(self.xy)
-        width = self.colwidth
-        height = self.colheight
-        addr = id(self)
-
-        format = "<%(class_name)s '%(nodeid)s' %(xy)s " + \
-                 "%(width)dx%(height)d at 0x%(addr)08x>"
-        return format % locals()
+        _format = "<%s '%s' %s %dx%d at 0x%08x>"
+        params = (self.__class__.__name__, self.id, str(self.xy),
+                  self.colwidth, self.colheight, id(self))
+        return _format % params
 
     def set_color(self, color):
         self.color = images.color_to_rgb(color)
@@ -174,6 +144,7 @@ class DiagramNode(Element):
     shape = 'box'
     int_attrs = Element.int_attrs + ['rotate']
     linecolor = (0, 0, 0)
+    label_orientation = 'horizontal'
     desctable = []
     attrname = {}
 
@@ -190,14 +161,15 @@ class DiagramNode(Element):
         super(DiagramNode, cls).clear()
         cls.shape = 'box'
         cls.linecolor = (0, 0, 0)
+        cls.label_orientation = 'horizontal'
         cls.desctable = ['numbered', 'label', 'description']
         cls.attrname = dict(numbered='No', label='Name',
                             description='Description')
 
-    def __init__(self, id):
-        super(DiagramNode, self).__init__(id)
+    def __init__(self, elemid):
+        super(DiagramNode, self).__init__(elemid)
 
-        self.label = unquote(id) or ''
+        self.label = unquote(elemid) or ''
         self.numbered = None
         self.icon = None
         self.background = None
@@ -236,9 +208,17 @@ class DiagramNode(Element):
             msg = "WARNING: background image not found: %s\n" % value
             sys.stderr.write(msg)
 
-    def set_stacked(self, value):
+    def set_stacked(self, _):
         self.stacked = True
 
+    def set_label_orientation(self, value):
+        value = value.lower()
+        if value in ('horizontal', 'vertical'):
+            self.label_orientation = value
+        else:
+            msg = "WARNING: unknown label orientation: %s\n" % value
+            raise AttributeError(msg)
+
     def to_desctable(self):
         attrs = []
         for name in self.desctable:
@@ -259,8 +239,8 @@ class NodeGroup(Element):
         super(NodeGroup, cls).clear()
         cls.basecolor = (243, 152, 0)
 
-    def __init__(self, id):
-        super(NodeGroup, self).__init__(id)
+    def __init__(self, elemid):
+        super(NodeGroup, self).__init__(elemid)
 
         self.level = 0
         self.separated = False
@@ -458,16 +438,10 @@ class DiagramEdge(Base):
         self.thick = None
 
     def __repr__(self):
-        class_name = self.__class__.__name__
-        node1_id = self.node1.id
-        node1_xy = self.node1.xy
-        node2_id = self.node2.id
-        node2_xy = self.node2.xy
-        addr = id(self)
-
-        format = "<%(class_name)s '%(node1_id)s' %(node1_xy)s - " + \
-                 "'%(node2_id)s' %(node2_xy)s, at 0x%(addr)08x>"
-        return format % locals()
+        _format = "<%s '%s' %s - '%s' %s at 0x%08x>"
+        params = (self.__class__.__name__, self.node1.id, self.node1.xy,
+                  self.node2.id, self.node2.xy, id(self))
+        return _format % params
 
     def set_dir(self, value):
         value = value.lower()
@@ -517,13 +491,13 @@ class DiagramEdge(Base):
             msg = "WARNING: unknown edge hstyle: %s\n" % value
             raise AttributeError(msg)
 
-    def set_folded(self, value):
+    def set_folded(self, _):
         self.folded = True
 
-    def set_nofolded(self, value):
+    def set_nofolded(self, _):
         self.folded = False
 
-    def set_thick(self, value):
+    def set_thick(self, _):
         self.thick = 3
 
     @property
@@ -533,27 +507,27 @@ class DiagramEdge(Base):
 
         if node1.x > node2.x:
             if node1.y > node2.y:
-                dir = 'left-up'
+                _dir = 'left-up'
             elif node1.y == node2.y:
-                dir = 'left'
+                _dir = 'left'
             else:
-                dir = 'left-down'
+                _dir = 'left-down'
         elif node1.x == node2.x:
             if node1.y > node2.y:
-                dir = 'up'
+                _dir = 'up'
             elif node1.y == node2.y:
-                dir = 'same'
+                _dir = 'same'
             else:
-                dir = 'down'
+                _dir = 'down'
         else:
             if node1.y > node2.y:
-                dir = 'right-up'
+                _dir = 'right-up'
             elif node1.y == node2.y:
-                dir = 'right'
+                _dir = 'right'
             else:
-                dir = 'right-down'
+                _dir = 'right-down'
 
-        return dir
+        return _dir
 
     def to_desctable(self):
         label = "%s -> %s" % (self.node1.label, self.node2.label)
@@ -566,13 +540,15 @@ class Diagram(NodeGroup):
     _NodeGroup = NodeGroup
 
     classes = {}
+    shadow_style = 'blur'
     linecolor = (0, 0, 0)
-    int_attrs = NodeGroup.int_attrs + \
-                ['node_width', 'node_height', 'span_width', 'span_height']
+    int_attrs = (NodeGroup.int_attrs +
+                 ['node_width', 'node_height', 'span_width', 'span_height'])
 
     @classmethod
     def clear(cls):
-        super(NodeGroup, cls).clear()
+        super(Diagram, cls).clear()
+        cls.shadow_style = 'blur'
         cls.linecolor = (0, 0, 0)
         cls.classes = {}
 
@@ -588,7 +564,7 @@ class Diagram(NodeGroup):
 
     def set_plugin(self, name, attrs):
         try:
-            kwargs = dict([str(unquote(attr.name)), unquote(attr.value)] \
+            kwargs = dict([str(unquote(attr.name)), unquote(attr.value)]
                           for attr in attrs)
             plugins.load([name], diagram=self, **kwargs)
         except:
@@ -606,6 +582,14 @@ class Diagram(NodeGroup):
             msg = "WARNING: unknown node shape: %s\n" % value
             raise AttributeError(msg)
 
+    def set_default_label_orientation(self, value):
+        value = value.lower()
+        if value in ('horizontal', 'vertical'):
+            DiagramNode.label_orientation = value
+        else:
+            msg = "WARNING: unknown label orientation: %s\n" % value
+            raise AttributeError(msg)
+
     def set_default_text_color(self, color):
         msg = "WARNING: default_text_color is obsoleted; " + \
               "use default_textcolor\n"
@@ -650,6 +634,14 @@ class Diagram(NodeGroup):
         self._NodeGroup.set_default_fontsize(fontsize)
         self._DiagramEdge.set_default_fontsize(fontsize)
 
+    def set_shadow_style(self, value):
+        value = value.lower()
+        if value in ('solid', 'blur', 'none'):
+            self.shadow_style = value
+        else:
+            msg = "WARNING: unknown shadow style: %s\n" % value
+            raise AttributeError(msg)
+
     def set_edge_layout(self, value):
         value = value.lower()
         if value in ('normal', 'flowchart'):
diff --git a/src/blockdiag/imagedraw/__init__.py b/src/blockdiag/imagedraw/__init__.py
index e921163..7534957 100644
--- a/src/blockdiag/imagedraw/__init__.py
+++ b/src/blockdiag/imagedraw/__init__.py
@@ -13,29 +13,39 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+import pkg_resources
+
 drawers = {}
 
-try:
-    from png import ImageDrawEx
-    drawers['png'] = ImageDrawEx
-except ImportError:
-    pass
 
-try:
-    from svg import SVGImageDraw
-    drawers['svg'] = SVGImageDraw
-except RuntimeError:
-    pass
+def init_imagedrawers():
+    for drawer in pkg_resources.iter_entry_points('blockdiag_imagedrawers'):
+        try:
+            module = drawer.load()
+            if hasattr(module, 'setup'):
+                module.setup(module)
+        except:
+            pass
+
+
+def install_imagedrawer(ext, drawer):
+    drawers[ext] = drawer
 
-try:
-    from pdf import PDFImageDraw
-    drawers['pdf'] = PDFImageDraw
-except ImportError:
-    pass
 
+def create(_format, filename, **kwargs):
+    if len(drawers) == 0:
+        init_imagedrawers()
 
-def create(format, filename, size, **kwargs):
-    if format.lower() in drawers:
-        return drawers[format.lower()](filename, size, **kwargs)
+    _format = _format.lower()
+    if _format in drawers:
+        drawer = drawers[_format](filename, **kwargs)
     else:
-        return None
+        msg = 'failed to load %s image driver' % _format
+        raise RuntimeError(msg)
+
+    if 'linejump' in kwargs.get('filters', []):
+        from blockdiag.imagedraw.filters.linejump import LineJumpDrawFilter
+        jumpsize = kwargs.get('jumpsize', 0)
+        drawer = LineJumpDrawFilter(drawer, jumpsize)
+
+    return drawer
diff --git a/src/blockdiag/imagedraw/base.py b/src/blockdiag/imagedraw/base.py
new file mode 100644
index 0000000..030c401
--- /dev/null
+++ b/src/blockdiag/imagedraw/base.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+#  Copyright 2011 Takeshi KOMIYA
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+from blockdiag.imagedraw import textfolder
+from blockdiag.utils import Box
+from blockdiag.utils.functools import partial
+
+
+class ImageDraw(object):
+    self_generative_methods = []
+    nosideeffect_methods = ['set_canvas_size', 'textsize', 'textlinesize']
+    supported_path = False
+    baseline_text_rendering = False
+
+    _method_cache = {}
+
+    def set_canvas_size(self, size):
+        pass
+
+    def set_options(self, **kwargs):
+        pass
+
+    def line(self, xy, **kwargs):
+        pass
+
+    def rectangle(self, box, **kwargs):
+        pass
+
+    def polygon(self, xy, **kwargs):
+        pass
+
+    def arc(self, xy, start, end, **kwargs):
+        pass
+
+    def ellipse(self, xy, **kwargs):
+        pass
+
+    def textsize(self, string, font, maxwidth=None, **kwargs):
+        if maxwidth is None:
+            maxwidth = 65535
+
+        box = Box(0, 0, maxwidth, 65535)
+        textbox = self.textfolder(box, string, font, **kwargs)
+        return textbox.outlinebox.size
+
+    @property
+    def textfolder(self):
+        return partial(textfolder.get, self,
+                       adjustBaseline=self.baseline_text_rendering)
+
+    def textlinesize(self, string, font, **kwargs):
+        pass
+
+    def text(self, xy, string, font, **kwargs):
+        pass
+
+    def textarea(self, box, string, font, **kwargs):
+        pass
+
+    def image(self, box, url):
+        pass
+
+    def loadImage(self, filename, box):  # TODO: obsoleted
+        return self.image(box, filename)
+
+    def save(self, filename, size, _format):
+        pass
diff --git a/src/blockdiag/imagedraw/filters/linejump.py b/src/blockdiag/imagedraw/filters/linejump.py
index a9f6703..d52c009 100644
--- a/src/blockdiag/imagedraw/filters/linejump.py
+++ b/src/blockdiag/imagedraw/filters/linejump.py
@@ -13,7 +13,7 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-from blockdiag.utils import XY
+from blockdiag.utils import functools, Box, XY
 
 
 class LazyReciever(object):
@@ -38,15 +38,18 @@ class LazyReciever(object):
                 self.calls.append((method, args, kwargs))
                 return self
 
-        return _
+        if method.__name__ in self.target.nosideeffect_methods:
+            return functools.partial(method, self.target)
+        else:
+            return _
 
     def _find_method(self, name):
         for p in self.target.__class__.__mro__:
             if name in p.__dict__:
                 return p.__dict__[name]
 
-        raise AttributeError("%s instance has no attribute '%s'"
-            % (self.target.__class__.__name__, name))
+        raise AttributeError("%s instance has no attribute '%s'" %
+                             (self.target.__class__.__name__, name))
 
     def _run(self):
         for recv in self.nested:
@@ -66,6 +69,16 @@ class LineJumpDrawFilter(LazyReciever):
         self.jump_radius = jump_radius
         self.jump_shift = 0
 
+    def set_options(self, **kwargs):
+        if 'jump_forward' in kwargs:
+            self.forward = kwargs['jump_forward']
+
+        if 'jump_radius' in kwargs:
+            self.jump_radius = kwargs['jump_radius']
+
+        if 'jump_shift' in kwargs:
+            self.jump_shift = kwargs['jump_shift']
+
     def _run(self):
         for recv in self.nested:
             recv._run()
@@ -97,7 +110,7 @@ class LineJumpDrawFilter(LazyReciever):
                 r = self.jump_radius
                 line = (XY(x1, y), XY(x - r, y))
                 self.target.line(line, **kwargs)
-                box = (x - r, y - r, x + r, y + r)
+                box = Box(x - r, y - r, x + r, y + r)
                 self.target.arc(box, 180, 0, **arckwargs)
                 x1 = x + r
 
@@ -117,7 +130,7 @@ class LineJumpDrawFilter(LazyReciever):
                 r = self.jump_radius
                 line = (XY(x, y1), XY(x, y - r))
                 self.target.line(line, **kwargs)
-                box = (x - r, y - r, x + r, y + r)
+                box = Box(x - r, y - r, x + r, y + r)
                 self.target.arc(box, 270, 90, **arckwargs)
                 y1 = y + r
 
@@ -128,7 +141,7 @@ class LineJumpDrawFilter(LazyReciever):
         for st, ed in zip(xy[:-1], xy[1:]):
             self.get_lazy_method("line")((st, ed), **kwargs)
 
-            if 'jump' in kwargs and kwargs['jump'] == True:
+            if 'jump' in kwargs and kwargs['jump'] is True:
                 if st.y == ed.y:    # horizonal
                     insort(self.ytree, (st.y, 0, (st, ed)))
                 elif st.x == ed.x:  # vertical
diff --git a/src/blockdiag/imagedraw/pdf.py b/src/blockdiag/imagedraw/pdf.py
index 377ae08..0daaa53 100644
--- a/src/blockdiag/imagedraw/pdf.py
+++ b/src/blockdiag/imagedraw/pdf.py
@@ -15,22 +15,29 @@
 
 import re
 import sys
+import math
 from reportlab.pdfgen import canvas
 from reportlab.pdfbase import pdfmetrics
 from reportlab.pdfbase.ttfonts import TTFont
-from blockdiag.utils import urlutil, Box
+from blockdiag.imagedraw import base
+from blockdiag.imagedraw.utils import cached
+from blockdiag.utils import urlutil, Box, Size
 from blockdiag.utils.fontmap import parse_fontpath
-from blockdiag.utils.PDFTextFolder import PDFTextFolder as TextFolder
+from blockdiag.utils.functools import partial
 
 
-class PDFImageDraw(object):
-    self_generative_methods = []
+class PDFImageDraw(base.ImageDraw):
+    baseline_text_rendering = True
 
-    def __init__(self, filename, size, **kwargs):
-        self.canvas = canvas.Canvas(filename, pagesize=size)
-        self.size = size
+    def __init__(self, filename, **kwargs):
+        self.filename = filename
+        self.canvas = None
         self.fonts = {}
 
+    def set_canvas_size(self, size):
+        self.canvas = canvas.Canvas(self.filename, pagesize=size)
+        self.size = size
+
     def set_font(self, font):
         if font.path is None:
             msg = "Could not detect fonts, use --font opiton\n"
@@ -119,6 +126,11 @@ class PDFImageDraw(object):
         if 'thick' in kwargs:
             self.canvas.setLineWidth(1)
 
+    @cached
+    def textlinesize(self, string, font):
+        width = self.canvas.stringWidth(string, font.path, font.size)
+        return Size(int(math.ceil(width)), font.size)
+
     def text(self, xy, string, font, **kwargs):
         self.set_font(font)
         self.set_fill_color(kwargs.get('fill'))
@@ -142,8 +154,7 @@ class PDFImageDraw(object):
                 box = box.shift(x=-self.size.y, y=self.size.y)
 
         self.set_font(font)
-        lines = TextFolder(box, string, font, adjustBaseline=True,
-                           canvas=self.canvas, **kwargs)
+        lines = self.textfolder(box, string, font, **kwargs)
 
         if kwargs.get('outline'):
             outline = kwargs.get('outline')
@@ -179,7 +190,7 @@ class PDFImageDraw(object):
         start, end = 360 - end, 360 - start
         r = (360 + end - start) % 360
 
-        params = self.set_render_params(**kwargs)
+        self.set_render_params(**kwargs)
         y = self.size[1]
         self.canvas.arc(xy[0], y - xy[3], xy[2], y - xy[1], start, r)
 
@@ -198,25 +209,27 @@ class PDFImageDraw(object):
         params = self.set_render_params(**kwargs)
         self.canvas.drawPath(pd, **params)
 
-    def loadImage(self, filename, box):
-        x = box[0]
-        y = self.size[1] - box[3]
-        w = box[2] - box[0]
-        h = box[3] - box[1]
-
-        if urlutil.isurl(filename):
+    def image(self, box, url):
+        if urlutil.isurl(url):
             from reportlab.lib.utils import ImageReader
             try:
-                filename = ImageReader(filename)
+                url = ImageReader(url)
             except:
-                msg = "WARNING: Could not retrieve: %s\n" % filename
+                msg = "WARNING: Could not retrieve: %s\n" % url
                 sys.stderr.write(msg)
                 return
-        self.canvas.drawImage(filename, x, y, w, h, mask='auto',
-                                  preserveAspectRatio=True)
 
-    def save(self, filename, size, format):
+        y = self.size[1] - box[3]
+        self.canvas.drawImage(url, box.x1, y, box.width, box.height,
+                              mask='auto', preserveAspectRatio=True)
+
+    def save(self, filename, size, _format):
         # Ignore size and format parameter; compatibility for ImageDrawEx.
 
         self.canvas.showPage()
         self.canvas.save()
+
+
+def setup(self):
+    from blockdiag.imagedraw import install_imagedrawer
+    install_imagedrawer('pdf', PDFImageDraw)
diff --git a/src/blockdiag/imagedraw/png.py b/src/blockdiag/imagedraw/png.py
index d233d23..bfcb8c1 100644
--- a/src/blockdiag/imagedraw/png.py
+++ b/src/blockdiag/imagedraw/png.py
@@ -16,10 +16,14 @@
 import re
 import math
 from itertools import izip, tee
-from blockdiag.utils import ellipse, urlutil, Box
+from blockdiag.imagedraw import base
+from blockdiag.imagedraw.utils import cached, ellipse
+from blockdiag.imagedraw.utils.ellipse import dots as ellipse_dots
+from blockdiag.utils import urlutil, Box, Size, XY
 from blockdiag.utils.fontmap import parse_fontpath, FontMap
+from blockdiag.utils.functools import partial, wraps
 from blockdiag.utils.myitertools import istep, stepslice
-from blockdiag.utils.PILTextFolder import PILTextFolder as TextFolder
+
 try:
     from PIL import Image
     from PIL import ImageDraw
@@ -101,38 +105,56 @@ def style2cycle(style, thick):
     return length
 
 
-class ImageDrawEx(object):
-    self_generative_methods = []
-
-    def __init__(self, filename, size, **kwargs):
-        if kwargs.get('im'):
-            self.image = kwargs.get('im')
+def ttfont_for(font):
+    if font.path:
+        path, index = parse_fontpath(font.path)
+        if index:
+            ttfont = ImageFont.truetype(path, font.size, index=index)
         else:
-            self.image = Image.new('RGB', size, (256, 256, 256))
+            ttfont = ImageFont.truetype(path, font.size)
+    else:
+        ttfont = None
 
-            # set transparency to background
-            if kwargs.get('transparency'):
-                alpha = Image.new('L', size, 1)
-                self.image.putalpha(alpha)
+    return ttfont
 
+
+class ImageDrawExBase(base.ImageDraw):
+    def __init__(self, filename, **kwargs):
         self.filename = filename
-        self.scale_ratio = kwargs.get('scale_ratio', 1)
-        self.mode = kwargs.get('mode')
-        self.draw = ImageDraw.ImageDraw(self.image, self.mode)
+        self.transparency = kwargs.get('transparency')
+        self.bgcolor = kwargs.get('color', (256, 256, 256))
+        self._image = None
+        self.draw = None
 
-        if 'parent' in kwargs:
-            parent = kwargs['parent']
-            self.scale_ratio = parent.scale_ratio
+        if kwargs.get('parent'):
+            self.scale_ratio = kwargs.get('parent').scale_ratio
+        else:
+            self.scale_ratio = kwargs.get('scale_ratio', 1)
 
-    def resizeCanvas(self, size):
-        self.image = self.image.resize(size, Image.ANTIALIAS)
-        self.draw = ImageDraw.ImageDraw(self.image, self.mode)
+        self.set_canvas_size(Size(1, 1))  # This line make textsize() workable
 
-    def smoothCanvas(self):
-        for i in range(15):
-            self.image = self.image.filter(ImageFilter.SMOOTH_MORE)
+    def paste(self, image, pt, mask=None):
+        self._image.paste(image, pt, mask)
+        self.draw = ImageDraw.Draw(self._image)
 
-        self.draw = ImageDraw.ImageDraw(self.image, self.mode)
+    def set_canvas_size(self, size):
+        if self.transparency:
+            mode = 'RGBA'
+        else:
+            mode = 'RGB'
+
+        self._image = Image.new(mode, size, self.bgcolor)
+
+        # set transparency to background
+        if self.transparency:
+            alpha = Image.new('L', size, 1)
+            self._image.putalpha(alpha)
+
+        self.draw = ImageDraw.Draw(self._image)
+
+    def resizeCanvas(self, size):
+        self._image = self._image.resize(size, Image.ANTIALIAS)
+        self.draw = ImageDraw.Draw(self._image)
 
     def arc(self, box, start, end, **kwargs):
         style = kwargs.get('style')
@@ -146,7 +168,7 @@ class ImageDrawEx(object):
                 end += 360
 
             cycle = style2cycle(style, kwargs.get('width'))
-            for pt in ellipse.dots(box, cycle, start, end):
+            for pt in ellipse_dots(box, cycle, start, end):
                 self.draw.line([pt, pt], fill=kwargs['fill'])
         else:
             self.draw.arc(box, start, end, **kwargs)
@@ -171,7 +193,7 @@ class ImageDrawEx(object):
                 del kwargs['outline']
 
             cycle = style2cycle(style, kwargs.get('width'))
-            for pt in ellipse.dots(box, cycle):
+            for pt in ellipse_dots(box, cycle):
                 self.draw.line([pt, pt], fill=kwargs['fill'])
         else:
             if kwargs.get('fill') == 'none':
@@ -190,8 +212,8 @@ class ImageDrawEx(object):
         style = kwargs.get('style')
         if kwargs.get('fill') == 'none':
             pass
-        elif style in ('dotted', 'dashed', 'none') or \
-             re.search('^\d+(,\d+)*$', style or ""):
+        elif (style in ('dotted', 'dashed', 'none') or
+              re.search('^\d+(,\d+)*$', style or "")):
             self.dashed_line(xy, **kwargs)
         else:
             if 'style' in kwargs:
@@ -248,17 +270,29 @@ class ImageDrawEx(object):
             del kwargs['outline']
             self.line(xy, **kwargs)
 
-    def text(self, xy, string, font, **kwargs):
-        fill = kwargs.get('fill')
+    @property
+    def textfolder(self):
+        textfolder = super(ImageDrawExBase, self).textfolder
+        return partial(textfolder, scale=self.scale_ratio)
 
-        if font.path:
-            path, index = parse_fontpath(font.path)
-            if index:
-                ttfont = ImageFont.truetype(path, font.size, index=index)
-            else:
-                ttfont = ImageFont.truetype(path, font.size)
+    @cached
+    def textlinesize(self, string, font):
+        ttfont = ttfont_for(font)
+        if ttfont is None:
+            size = self.draw.textsize(string, font=None)
+
+            font_ratio = font.size * 1.0 / FontMap.BASE_FONTSIZE
+            size = Size(int(size[0] * font_ratio),
+                        int(size[1] * font_ratio))
         else:
-            ttfont = None
+            size = self.draw.textsize(string, font=ttfont)
+            size = Size(*size)
+
+        return size
+
+    def text(self, xy, string, font, **kwargs):
+        fill = kwargs.get('fill')
+        ttfont = ttfont_for(font)
 
         if ttfont is None:
             if self.scale_ratio == 1 and font.size == FontMap.BASE_FONTSIZE:
@@ -270,12 +304,10 @@ class ImageDrawEx(object):
                 draw.text((0, 0), string, fill=fill)
                 del draw
 
-                font_ratio = font.size * 1.0 / FontMap.BASE_FONTSIZE
-                basesize = (int(size[0] * self.scale_ratio * font_ratio),
-                            int(size[1] * self.scale_ratio * font_ratio))
+                basesize = (size[0] * self.scale_ratio,
+                            size[1] * self.scale_ratio)
                 text_image = image.resize(basesize, Image.ANTIALIAS)
-
-                self.image.paste(text_image, xy, text_image)
+                self.paste(text_image, xy, text_image)
         else:
             size = self.draw.textsize(string, font=ttfont)
 
@@ -286,9 +318,7 @@ class ImageDrawEx(object):
 
             # Rendering text
             filler = Image.new('RGB', size, fill)
-            self.image.paste(filler, xy, mask)
-
-            self.draw = ImageDraw.ImageDraw(self.image, self.mode)
+            self.paste(filler, xy, mask)
 
     def textarea(self, box, string, font, **kwargs):
         if 'rotate' in kwargs and kwargs['rotate'] != 0:
@@ -300,17 +330,16 @@ class ImageDrawEx(object):
             else:
                 _box = box
 
-            text = ImageDrawEx(None, _box.size, parent=self, mode=self.mode,
-                               transparency=True)
-            textbox = (0, 0, _box.width, _box.height)
+            text = ImageDrawEx(None, parent=self, transparency=True)
+            text.set_canvas_size(_box.size)
+            textbox = Box(0, 0, _box.width, _box.height)
             text.textarea(textbox, string, font, **kwargs)
 
-            filter = Image.new('RGB', box.size, kwargs.get('fill'))
-            self.image.paste(filter, box.topleft, text.image.rotate(angle))
-            self.draw = ImageDraw.ImageDraw(self.image, self.mode)
+            filler = Image.new('RGB', box.size, kwargs.get('fill'))
+            self.paste(filler, box.topleft, text._image.rotate(angle))
             return
 
-        lines = TextFolder(box, string, font, scale=self.scale_ratio, **kwargs)
+        lines = self.textfolder(box, string, font, **kwargs)
 
         if kwargs.get('outline'):
             outline = kwargs.get('outline')
@@ -325,60 +354,122 @@ class ImageDrawEx(object):
             font.size = int(font.size * 0.8)
             self.textarea(box, string, font, **kwargs)
 
-    def loadImage(self, filename, box):
-        box_width = box[2] - box[0]
-        box_height = box[3] - box[1]
-
-        if urlutil.isurl(filename):
+    def image(self, box, url):
+        if urlutil.isurl(url):
             import cStringIO
             import urllib
             try:
-                filename = cStringIO.StringIO(urllib.urlopen(filename).read())
+                url = cStringIO.StringIO(urllib.urlopen(url).read())
             except:
                 import sys
-                msg = "WARNING: Could not retrieve: %s\n" % filename
+                msg = "WARNING: Could not retrieve: %s\n" % url
                 sys.stderr.write(msg)
                 return
-        image = Image.open(filename)
+        image = Image.open(url)
 
         # resize image.
-        w = min([box_width, image.size[0] * self.scale_ratio])
-        h = min([box_height, image.size[1] * self.scale_ratio])
+        w = min([box.width, image.size[0] * self.scale_ratio])
+        h = min([box.height, image.size[1] * self.scale_ratio])
         image.thumbnail((w, h), Image.ANTIALIAS)
 
         # centering image.
         w, h = image.size
-        if box_width > w:
-            x = box[0] + (box_width - w) / 2
+        if box.width > w:
+            x = box[0] + (box.width - w) / 2
         else:
             x = box[0]
 
-        if box_height > h:
-            y = box[1] + (box_height - h) / 2
+        if box.height > h:
+            y = box[1] + (box.height - h) / 2
         else:
             y = box[1]
 
-        self.image.paste(image, (x, y))
-        self.draw = ImageDraw.ImageDraw(self.image, self.mode)
+        self.paste(image, (x, y))
 
-    def save(self, filename, size, format):
+    def save(self, filename, size, _format):
         if filename:
             self.filename = filename
 
         if size is None:
-            x = int(self.image.size[0] / self.scale_ratio)
-            y = int(self.image.size[1] / self.scale_ratio)
+            x = int(self._image.size[0] / self.scale_ratio)
+            y = int(self._image.size[1] / self.scale_ratio)
             size = (x, y)
 
-        self.image.thumbnail(size, Image.ANTIALIAS)
+        self._image.thumbnail(size, Image.ANTIALIAS)
 
         if self.filename:
-            self.image.save(self.filename, format)
+            self._image.save(self.filename, _format)
             image = None
         else:
             import cStringIO
             tmp = cStringIO.StringIO()
-            self.image.save(tmp, format)
+            self._image.save(tmp, _format)
             image = tmp.getvalue()
 
         return image
+
+
+def blurred(fn):
+    PADDING = 16
+
+    def get_shape_box(*args):
+        if fn.__name__ == 'polygon':
+            xlist = [pt.x for pt in args[0]]
+            ylist = [pt.y for pt in args[0]]
+            return Box(min(xlist), min(ylist), max(xlist), max(ylist))
+        else:
+            return args[0]
+
+    def get_abs_coordinate(box, *args):
+        dx = box.x1 - PADDING
+        dy = box.y1 - PADDING
+        if fn.__name__ == 'polygon':
+            return [pt.shift(-dx, -dy) for pt in args[0]]
+        else:
+            return box.shift(-dx, -dy)
+
+    def create_shadow(self, size, *args, **kwargs):
+        drawer = ImageDrawExBase(self.filename, transparency=True)
+        drawer.set_canvas_size(size)
+        getattr(drawer, fn.__name__)(*args, **kwargs)
+
+        for _ in range(15):
+            drawer._image = drawer._image.filter(ImageFilter.SMOOTH_MORE)
+
+        return drawer._image
+
+    @wraps(fn)
+    def func(self, *args, **kwargs):
+        args = list(args)
+
+        if kwargs.get('filter') not in ('blur', 'transp-blur'):
+            return fn(self, *args, **kwargs)
+        else:
+            box = get_shape_box(*args)
+            args[0] = get_abs_coordinate(box, *args)
+
+            size = Size(box.width + PADDING * 2, box.height + PADDING * 2)
+            shadow = create_shadow(self, size, *args, **kwargs)
+            xy = XY(box.x1 - PADDING, box.y1 - PADDING)
+            self.paste(shadow, xy, shadow)
+
+    return func
+
+
+class ImageDrawEx(ImageDrawExBase):
+    @blurred
+    def ellipse(self, box, **kwargs):
+        super(ImageDrawEx, self).ellipse(box, **kwargs)
+
+    @blurred
+    def rectangle(self, box, **kwargs):
+        super(ImageDrawEx, self).rectangle(box, **kwargs)
+
+    @blurred
+    def polygon(self, xy, **kwargs):
+        super(ImageDrawEx, self).polygon(xy, **kwargs)
+
+
+def setup(self):
+    from blockdiag.imagedraw import install_imagedrawer
+    install_imagedrawer('png', ImageDrawEx)
diff --git a/src/blockdiag/imagedraw/simplesvg.py b/src/blockdiag/imagedraw/simplesvg.py
index 6a2e48c..273a297 100644
--- a/src/blockdiag/imagedraw/simplesvg.py
+++ b/src/blockdiag/imagedraw/simplesvg.py
@@ -59,13 +59,19 @@ class base(object):
             if value is not None:
                 io.write(' %s=%s' % (_escape(key), _quote(value)))
 
-        if self.elements == [] and self.text is None:
-            io.write(" />\n")
-        elif self.text is not None:
-            text = _escape(self.text).encode('utf-8')
-            io.write(">%s</%s>\n" % (text, clsname))
+        if self.elements == []:
+            if self.text is not None:
+                text = _escape(self.text).encode('utf-8')
+                io.write(">%s</%s>\n" % (text, clsname))
+            else:
+                io.write(" />\n")
         elif self.elements:
-            io.write(">\n")
+            if self.text is not None:
+                text = _escape(self.text).encode('utf-8')
+                io.write(">%s\n" % (text,))
+            else:
+                io.write(">\n")
+
             for e in self.elements:
                 e.to_xml(io, level + 1)
             io.write('%s</%s>\n' % (indent, clsname))
@@ -83,17 +89,20 @@ class element(base):
 
 
 class svg(base):
-    def __init__(self, x, y, width, height):
-        viewbox = "%d %d %d %d" % (x, y, width, height)
-        super(svg, self).__init__(viewBox=viewbox)
+    def __init__(self, x, y, width, height, **kwargs):
+        if kwargs.get('noviewbox'):
+            super(svg, self).__init__(width=(width - x), height=(height - y))
+        else:
+            viewbox = "%d %d %d %d" % (x, y, width, height)
+            super(svg, self).__init__(viewBox=viewbox)
 
-        self.use_doctype = True
+        self.nodoctype = kwargs.get('nodoctype', False)
         self.add_attribute('xmlns', 'http://www.w3.org/2000/svg')
 
     def to_xml(self):
         io = cStringIO.StringIO()
 
-        if self.use_doctype:
+        if not self.nodoctype:
             url = "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
             io.write("<?xml version='1.0' encoding='UTF-8'?>\n")
             io.write('<!DOCTYPE svg PUBLIC '
@@ -170,7 +179,7 @@ class pathdata:
         self.path.append('H%s' % (x,))
 
     def relhline(self, x):
-        self.path.append('h%s %s' % (x,))
+        self.path.append('h%s' % (x,))
 
     def vline(self, y):
         self.path.append('V%s' % (y,))
@@ -203,11 +212,11 @@ class pathdata:
         self.path.append('t%s %s' % (x, y))
 
     def ellarc(self, rx, ry, xrot, laf, sf, x, y):
-        self.path.append('A%s,%s %s %s %s %s %s' % \
+        self.path.append('A%s,%s %s %s %s %s %s' %
                          (rx, ry, xrot, laf, sf, x, y))
 
     def relellarc(self, rx, ry, xrot, laf, sf, x, y):
-        self.path.append('a%s,%s %s %s %s %s %s' % \
+        self.path.append('a%s,%s %s %s %s %s %s' %
                          (rx, ry, xrot, laf, sf, x, y))
 
     def __repr__(self):
diff --git a/src/blockdiag/imagedraw/svg.py b/src/blockdiag/imagedraw/svg.py
index 4ffddcc..1168572 100644
--- a/src/blockdiag/imagedraw/svg.py
+++ b/src/blockdiag/imagedraw/svg.py
@@ -15,172 +15,187 @@
 
 import re
 import base64
+from blockdiag.imagedraw import base as _base
+from blockdiag.imagedraw.simplesvg import *
+from blockdiag.imagedraw.utils import cached
+from blockdiag.imagedraw.utils.ellipse import endpoints as ellipse_endpoints
 from blockdiag.utils import urlutil, Box, XY
-from simplesvg import *
-
-try:
-    from blockdiag.utils.PILTextFolder import PILTextFolder as TextFolder
-except ImportError:
-    from blockdiag.utils.TextFolder import TextFolder
+from blockdiag.utils.functools import partial
 
 feGaussianBlur = svgclass('feGaussianBlur')
 
 
-class SVGImageDrawElement(object):
-    self_generative_methods = ['group', 'anchor']
+def rgb(color):
+    if isinstance(color, tuple):
+        color = 'rgb(%d,%d,%d)' % color
 
-    def __init__(self, svg, parent=None):
-        self.svg = svg
+    return color
 
-    def rgb(self, color):
-        if isinstance(color, tuple):
-            color = 'rgb(%d,%d,%d)' % color
 
-        return color
+def style(name):
+    if name == 'blur':
+        value = "filter:url(#filter_blur)"
+    elif name == 'transp-blur':
+        value = "filter:url(#filter_blur);opacity:0.7;fill-opacity:1"
+    else:
+        value = None
+
+    return value
+
+
+def dasharray(pattern, thick):
+    if thick is None:
+        thick = 1
+
+    if pattern == 'dotted':
+        value = 2 * thick
+    elif pattern == 'dashed':
+        value = 4 * thick
+    elif pattern == 'none':
+        value = "%d %d" % (0, 65535 * thick)
+    elif re.search('^\d+(,\d+)*$', pattern or ""):
+        l = [int(n) * thick for n in pattern.split(",")]
+        value = " ".join(str(n) for n in l)
+    else:
+        value = None
+
+    return value
 
-    def filter(self, name):
-        if name == 'blur':
-            filter = "filter:url(#filter_blur)"
-        elif name == 'transp-blur':
-            filter = "filter:url(#filter_blur);opacity:0.7;fill-opacity:1"
-        else:
-            filter = None
-
-        return filter
-
-    def style(self, name, thick):
-        if thick is None:
-            thick = 1
-
-        if name == 'dotted':
-            length = 2 * thick
-        elif name == 'dashed':
-            length = 4 * thick
-        elif name == 'none':
-            length = "%d %d" % (0, 65535 * thick)
-        elif re.search('^\d+(,\d+)*$', name or ""):
-            l = [int(n) * thick for n in name.split(",")]
-            length = " ".join(str(n) for n in l)
-        else:
-            length = None
 
-        return length
+def drawing_params(kwargs):
+    params = {}
+
+    if 'style' in kwargs:
+        params['stroke_dasharray'] = dasharray(kwargs.get('style'),
+                                               kwargs.get('thick'))
+
+    if 'filter' in kwargs:
+        params['style'] = style(kwargs.get('filter'))
+
+    return params
+
+
+class SVGImageDrawElement(_base.ImageDraw):
+    self_generative_methods = ['group', 'anchor']
+    supported_path = True
+    baseline_text_rendering = True
+
+    def __init__(self, svg, parent=None):
+        self.svg = svg
+        if parent and parent.ignore_pil:
+            self.ignore_pil = True
+        else:
+            self.ignore_pil = False
 
     def path(self, pd, **kwargs):
-        thick = kwargs.get('thick')
         fill = kwargs.get('fill')
         outline = kwargs.get('outline')
-        style = kwargs.get('style')
-        filter = kwargs.get('filter')
 
-        p = path(pd, fill=self.rgb(fill), stroke=self.rgb(outline),
-                 stroke_dasharray=self.style(style, thick),
-                 style=self.filter(filter))
+        p = path(pd, fill=rgb(fill), stroke=rgb(outline),
+                 **drawing_params(kwargs))
         self.svg.addElement(p)
 
     def rectangle(self, box, **kwargs):
         thick = kwargs.get('thick')
         fill = kwargs.get('fill', 'none')
         outline = kwargs.get('outline')
-        style = kwargs.get('style')
-        filter = kwargs.get('filter')
-
-        x = box[0]
-        y = box[1]
-        width = box[2] - box[0]
-        height = box[3] - box[1]
-
-        r = rect(x, y, width, height, fill=self.rgb(fill),
-                 stroke=self.rgb(outline), stroke_width=thick,
-                 stroke_dasharray=self.style(style, thick),
-                 style=self.filter(filter))
+
+        r = rect(box.x, box.y, box.width, box.height,
+                 fill=rgb(fill), stroke=rgb(outline),
+                 stroke_width=thick, **drawing_params(kwargs))
         self.svg.addElement(r)
 
-    def text(self, xy, string, font, **kwargs):
+    @cached
+    def textlinesize(self, string, font, **kwargs):
+        if kwargs.get('ignore_pil', self.ignore_pil):
+            from blockdiag.imagedraw.utils import textsize
+            return textsize(string, font)
+        else:
+            if not hasattr(self, '_pil_drawer'):
+                from blockdiag.imagedraw import png
+                self._pil_drawer = png.ImageDrawEx(None)
+
+            return self._pil_drawer.textlinesize(string, font)
+
+    def text(self, point, string, font, **kwargs):
         fill = kwargs.get('fill')
 
-        t = text(xy[0], xy[1], string, fill=self.rgb(fill),
+        t = text(point.x, point.y, string, fill=rgb(fill),
                  font_family=font.generic_family, font_size=font.size,
                  font_weight=font.weight, font_style=font.style)
         self.svg.addElement(t)
 
     def textarea(self, box, string, font, **kwargs):
         if 'rotate' in kwargs and kwargs['rotate'] != 0:
-            angle = int(kwargs['rotate']) % 360
-            del kwargs['rotate']
-
-            if angle in (90, 270):
-                _box = Box(box[0], box[1],
-                           box[0] + box.height, box[1] + box.width)
-                if angle == 90:
-                    _box = _box.shift(x=box.width)
-                elif angle == 270:
-                    _box = _box.shift(y=box.height)
-            elif angle == 180:
-                _box = Box(box[2], box[3],
-                           box[2] + box.width, box[3] + box.height)
-            else:
-                _box = Box(box[0], box[1],
-                           box[0] + box.width, box[1] + box.height)
-
-            rotate = "rotate(%d,%d,%d)" % (angle, _box[0], _box[1])
-            group = g(transform="%s" % rotate)
-            self.svg.addElement(group)
-
-            elem = SVGImageDrawElement(group, self)
-            elem.textarea(_box, string, font, **kwargs)
-            return
-
-        lines = TextFolder(box, string, font, adjustBaseline=True, **kwargs)
-
-        if kwargs.get('outline'):
-            outline = kwargs.get('outline')
-            self.rectangle(lines.outlinebox, fill='white', outline=outline)
-
-        rendered = False
-        for string, xy in lines.lines:
-            self.text(xy, string, font, **kwargs)
-            rendered = True
-
-        if not rendered and font.size > 0:
-            font.size = int(font.size * 0.8)
-            self.textarea(box, string, font, **kwargs)
-
-    def line(self, xy, **kwargs):
+            self.rotated_textarea(box, string, font, **kwargs)
+        else:
+            lines = self.textfolder(box, string, font, **kwargs)
+
+            if kwargs.get('outline'):
+                outline = kwargs.get('outline')
+                self.rectangle(lines.outlinebox, fill='white', outline=outline)
+
+            rendered = False
+            for string, point in lines.lines:
+                self.text(point, string, font, **kwargs)
+                rendered = True
+
+            if not rendered and font.size > 0:
+                font.size = int(font.size * 0.8)
+                self.textarea(box, string, font, **kwargs)
+
+    def rotated_textarea(self, box, string, font, **kwargs):
+        angle = int(kwargs['rotate']) % 360
+        del kwargs['rotate']
+
+        if angle in (90, 270):
+            _box = Box(box[0], box[1],
+                       box[0] + box.height, box[1] + box.width)
+            if angle == 90:
+                _box = _box.shift(x=box.width)
+            elif angle == 270:
+                _box = _box.shift(y=box.height)
+        elif angle == 180:
+            _box = Box(box[2], box[3],
+                       box[2] + box.width, box[3] + box.height)
+        else:
+            _box = Box(box[0], box[1],
+                       box[0] + box.width, box[1] + box.height)
+
+        rotate = "rotate(%d,%d,%d)" % (angle, _box[0], _box[1])
+        group = g(transform="%s" % rotate)
+        self.svg.addElement(group)
+
+        elem = SVGImageDrawElement(group, self)
+        elem.textarea(_box, string, font, **kwargs)
+
+    def line(self, points, **kwargs):
         fill = kwargs.get('fill')
-        style = kwargs.get('style')
         thick = kwargs.get('thick')
 
-        pd = pathdata(xy[0].x, xy[0].y)
-        for pt in xy[1:]:
+        pd = pathdata(points[0].x, points[0].y)
+        for pt in points[1:]:
             pd.line(pt.x, pt.y)
 
-        p = path(pd, fill="none", stroke=self.rgb(fill),
-                 stroke_width=thick, stroke_dasharray=self.style(style, thick))
+        p = path(pd, fill="none", stroke=rgb(fill),
+                 stroke_width=thick, **drawing_params(kwargs))
         self.svg.addElement(p)
 
-    def arc(self, xy, start, end, **kwargs):
+    def arc(self, box, start, end, **kwargs):
         thick = kwargs.get('thick')
         fill = kwargs.get('fill')
-        style = kwargs.get('style')
 
-        w = (xy[2] - xy[0]) / 2
-        h = (xy[3] - xy[1]) / 2
+        w = box.width / 2
+        h = box.height / 2
 
         if start > end:
             end += 360
 
-        from blockdiag.utils import ellipse
-
-        coord = ellipse.coordinate(1, w, h, start, start + 1)
-        point = iter(coord).next()
-        pt1 = XY(xy[0] + w + round(point[0], 0),
-                 xy[1] + h + round(point[1], 0))
-
-        coord = ellipse.coordinate(1, w, h, end, end + 1)
-        point = iter(coord).next()
-        pt2 = XY(xy[0] + w + round(point[0], 0),
-                 xy[1] + h + round(point[1], 0))
+        endpoints = ellipse_endpoints(1, w, h, start, end)
+        pt1 = XY(box.x + w + round(endpoints[0].x, 0),
+                 box.y + h + round(endpoints[0].y, 0))
+        pt2 = XY(box.x + w + round(endpoints[1].x, 0),
+                 box.y + h + round(endpoints[1].y, 0))
 
         if end - start > 180:
             largearc = 1
@@ -189,52 +204,36 @@ class SVGImageDrawElement(object):
 
         pd = pathdata(pt1[0], pt1[1])
         pd.ellarc(w, h, 0, largearc, 1, pt2[0], pt2[1])
-        p = path(pd, fill="none", stroke=self.rgb(fill),
-                 stroke_dasharray=self.style(style, thick))
+        p = path(pd, fill="none", stroke=rgb(fill),
+                 **drawing_params(kwargs))
         self.svg.addElement(p)
 
-    def ellipse(self, xy, **kwargs):
-        thick = kwargs.get('thick')
+    def ellipse(self, box, **kwargs):
         fill = kwargs.get('fill')
         outline = kwargs.get('outline')
-        style = kwargs.get('style')
-        filter = kwargs.get('filter')
 
-        w = (xy[2] - xy[0]) / 2
-        h = (xy[3] - xy[1]) / 2
-        pt = XY(xy[0] + w, xy[1] + h)
+        w = box.width / 2
+        h = box.height / 2
+        pt = box.center
 
-        e = ellipse(pt.x, pt.y, w, h, fill=self.rgb(fill),
-                    stroke=self.rgb(outline),
-                    stroke_dasharray=self.style(style, thick),
-                    style=self.filter(filter))
+        e = ellipse(pt.x, pt.y, w, h, fill=rgb(fill),
+                    stroke=rgb(outline), **drawing_params(kwargs))
         self.svg.addElement(e)
 
-    def polygon(self, xy, **kwargs):
-        thick = kwargs.get('thick')
+    def polygon(self, points, **kwargs):
         fill = kwargs.get('fill')
         outline = kwargs.get('outline')
-        style = kwargs.get('style')
-        filter = kwargs.get('filter')
 
-        pg = polygon(xy, fill=self.rgb(fill), stroke=self.rgb(outline),
-                     stroke_dasharray=self.style(style, thick),
-                     style=self.filter(filter))
+        pg = polygon(points, fill=rgb(fill), stroke=rgb(outline),
+                     **drawing_params(kwargs))
         self.svg.addElement(pg)
 
-    def loadImage(self, filename, box):
-        if urlutil.isurl(filename):
-            url = filename
-        else:
-            string = open(filename, 'rb').read()
+    def image(self, box, url):
+        if not urlutil.isurl(url):
+            string = open(url, 'rb').read()
             url = "data:;base64," + base64.b64encode(string)
 
-        x = box[0]
-        y = box[1]
-        w = box[2] - box[0]
-        h = box[3] - box[1]
-
-        im = image(url, x, y, w, h)
+        im = image(url, box.x1, box.y1, box.width, box.height)
         self.svg.addElement(im)
 
     def anchor(self, url):
@@ -242,40 +241,46 @@ class SVGImageDrawElement(object):
         a_node.add_attribute('xlink:href', url)
         self.svg.addElement(a_node)
 
-        return SVGImageDrawElement(a_node)
+        return SVGImageDrawElement(a_node, self)
 
     def group(self):
         group = g()
         self.svg.addElement(group)
 
-        return SVGImageDrawElement(group)
+        return SVGImageDrawElement(group, self)
 
 
 class SVGImageDraw(SVGImageDrawElement):
-    def __init__(self, filename, size, **kwargs):
+    def __init__(self, filename, **kwargs):
+        super(SVGImageDraw, self).__init__(None)
+
         self.filename = filename
-        super(SVGImageDraw, self).__init__(svg(0, 0, size[0], size[1]))
-        self.svg.use_doctype = not kwargs.get('nodoctype')
+        self.options = kwargs
+        self.ignore_pil = kwargs.get('ignore_pil')
+        self.set_canvas_size((0, 0))
 
+    def set_canvas_size(self, size):
+        self.svg = svg(0, 0, size[0], size[1], **self.options)
         uri = 'http://www.inkscape.org/namespaces/inkscape'
         self.svg.add_attribute('xmlns:inkspace', uri)
         uri = 'http://www.w3.org/1999/xlink'
         self.svg.add_attribute('xmlns:xlink', uri)
 
         # inkspace's Gaussian filter
-        fgb = feGaussianBlur(id='feGaussianBlur3780', stdDeviation=4.2)
-        fgb.add_attribute('inkspace:collect', 'always')
-        f = filter(-0.07875, -0.252, 1.1575, 1.504, id='filter_blur')
-        f.add_attribute('inkspace:collect', 'always')
-        f.addElement(fgb)
-        d = defs(id='defs_block')
-        d.addElement(f)
-        self.svg.addElement(d)
+        if self.options.get('style') != 'blur':
+            fgb = feGaussianBlur(id='feGaussianBlur3780', stdDeviation=4.2)
+            fgb.add_attribute('inkspace:collect', 'always')
+            f = filter(-0.07875, -0.252, 1.1575, 1.504, id='filter_blur')
+            f.add_attribute('inkspace:collect', 'always')
+            f.addElement(fgb)
+            d = defs(id='defs_block')
+            d.addElement(f)
+            self.svg.addElement(d)
 
         self.svg.addElement(title('blockdiag'))
-        self.svg.addElement(desc(kwargs.get('code')))
+        self.svg.addElement(desc(self.options.get('code')))
 
-    def save(self, filename, size, format):
+    def save(self, filename, size, _format):
         # Ignore format parameter; compatibility for ImageDrawEx.
 
         if filename:
@@ -285,9 +290,14 @@ class SVGImageDraw(SVGImageDrawElement):
             self.svg.attributes['width'] = size[0]
             self.svg.attributes['height'] = size[1]
 
-        svg = self.svg.to_xml()
+        image = self.svg.to_xml()
 
         if self.filename:
-            open(self.filename, 'w').write(svg)
+            open(self.filename, 'w').write(image)
+
+        return image
+
 
-        return svg
+def setup(self):
+    from blockdiag.imagedraw import install_imagedrawer
+    install_imagedrawer('svg', SVGImageDraw)
diff --git a/src/blockdiag/imagedraw/textfolder.py b/src/blockdiag/imagedraw/textfolder.py
new file mode 100644
index 0000000..83a1d8d
--- /dev/null
+++ b/src/blockdiag/imagedraw/textfolder.py
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+#  Copyright 2011 Takeshi KOMIYA
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import re
+from blockdiag.utils import Box, Size, XY
+
+
+def splitlabel(string):
+    """Split text to lines as generator.
+       Every line will be stripped.
+       If text includes characters "\n", treat as line separator.
+    """
+    string = re.sub('^\s*', '', string)
+    string = re.sub('\s*$', '', string)
+    string = re.sub('(?:\xa5|\\\\){2}', '\x00', string)
+    string = re.sub('(?:\xa5|\\\\)n', '\n', string)
+    for line in string.splitlines():
+        yield re.sub('\x00', '\\\\', line).strip()
+
+
+def splittext(metrics, text, bound, measure='width'):
+    folded = []
+    if text == '':
+        folded.append(u' ')
+
+    for i in range(len(text), 0, -1):
+        textsize = metrics.textsize(text[0:i])
+
+        if getattr(textsize, measure) <= bound:
+            folded.append(text[0:i])
+            if text[i:]:
+                folded += splittext(metrics, text[i:], bound, measure)
+            break
+
+    return folded
+
+
+def truncate_text(metrics, text, bound, measure='width'):
+    for i in range(len(text), 0, -1):
+        textsize = metrics.textsize(text[0:i] + ' ...')
+
+        if getattr(textsize, measure) <= bound:
+            return text[0:i] + ' ...'
+
+    return text
+
+
+def get(*args, **kwargs):
+    if kwargs.get('orientation') == 'vertical':
+        return VerticalTextFolder(*args, **kwargs)
+    else:
+        return HorizontalTextFolder(*args, **kwargs)
+
+
+class VerticalTextFolder(object):
+    def __init__(self, drawer, box, string, font, **kwargs):
+        self.drawer = drawer
+        self.box = box
+        self.string = string
+        self.font = font
+        self.scale = 1
+        self.halign = kwargs.get('halign', 'center')
+        self.valign = kwargs.get('valign', 'center')
+        self.padding = kwargs.get('padding', 8)
+        self.line_spacing = kwargs.get('line_spacing', 2)
+
+        if kwargs.get('adjustBaseline'):
+            self.adjustBaseline = True
+        else:
+            self.adjustBaseline = False
+
+        self._result = self._lines()
+
+    def textsize(self, text, scaled=False):
+        if isinstance(text, (str, unicode)):
+            size = [self.drawer.textlinesize(c, self.font) for c in text]
+            width = max(s.width for s in size)
+            height = (sum(s.height for s in size) +
+                      self.line_spacing * (len(text) - 1))
+
+            textsize = Size(width, height)
+        else:
+            if text:
+                size = [self.textsize(s) for s in text]
+                height = max(s.height for s in size)
+                width = (sum(s.width for s in size) +
+                         self.line_spacing * (len(text) - 1))
+
+                textsize = Size(width, height)
+            else:
+                textsize = Size(0, 0)
+
+        if scaled:
+            textsize = Size(textsize.width * self.scale,
+                            textsize.height * self.scale)
+
+        return textsize
+
+    @property
+    def lines(self):
+        textsize = self.textsize(self._result, scaled=True)
+
+        dx, _ = self.box.get_padding_for(textsize, halign=self.halign,
+                                         padding=self.padding)
+
+        width = self.box.width - dx + self.line_spacing
+        base_xy = XY(self.box.x1, self.box.y1)
+        for string in self._result:
+            textsize = self.textsize(string, scaled=True)
+            _, dy = self.box.get_padding_for(textsize, valign=self.valign,
+                                             padding=self.line_spacing)
+
+            height = dy
+            width -= textsize.width + self.line_spacing
+            for char in string:
+                charsize = self.textsize(char, scaled=True)
+
+                if self.adjustBaseline:
+                    draw_xy = base_xy.shift(width, height + charsize.height)
+                else:
+                    draw_xy = base_xy.shift(width, height)
+
+                yield char, draw_xy
+
+                height += charsize.height + self.line_spacing
+
+    @property
+    def outlinebox(self):
+        corners = []
+        for string, xy in self.lines:
+            textsize = self.textsize(string)
+            width = textsize[0] * self.scale
+            height = textsize[1] * self.scale
+
+            if self.adjustBaseline:
+                xy = XY(xy.x, xy.y - textsize[1])
+
+            corners.append(xy)
+            corners.append(XY(xy.x + width, xy.y + height))
+
+        if corners:
+            box = Box(min(p.x for p in corners) - self.padding,
+                      min(p.y for p in corners) - self.line_spacing,
+                      max(p.x for p in corners) + self.padding,
+                      max(p.y for p in corners) + self.line_spacing)
+        else:
+            box = Box(self.box[0], self.box[1], self.box[0], self.box[1])
+
+        return box
+
+    def _lines(self):
+        lines = []
+        measure = 'height'
+        maxwidth, maxheight = self.box.size
+
+        width = 0
+        finished = False
+        for line in splitlabel(self.string):
+            for folded in splittext(self, line, maxheight, measure):
+                textsize = self.textsize(folded)
+
+                if width + textsize.width + self.line_spacing < maxwidth:
+                    lines.append(folded)
+                    width += textsize.width + self.line_spacing
+                elif len(lines) > 0:
+                    lines[-1] = truncate_text(self, lines[-1],
+                                              maxheight, measure)
+                    finished = True
+                    break
+
+            if finished:
+                break
+
+        return lines
+
+
+class HorizontalTextFolder(object):
+    def __init__(self, drawer, box, string, font, **kwargs):
+        self.drawer = drawer
+        self.box = box
+        self.string = string
+        self.font = font
+        self.scale = 1
+        self.halign = kwargs.get('halign', 'center')
+        self.valign = kwargs.get('valign', 'center')
+        self.padding = kwargs.get('padding', 8)
+        self.line_spacing = kwargs.get('line_spacing', 2)
+
+        if kwargs.get('adjustBaseline'):
+            self.adjustBaseline = True
+        else:
+            self.adjustBaseline = False
+
+        self._result = self._lines()
+
+    def textsize(self, text, scaled=False):
+        if isinstance(text, (str, unicode)):
+            textsize = self.drawer.textlinesize(text, self.font)
+        else:
+            if text:
+                size = [self.textsize(s) for s in text]
+                width = max(s.width for s in size)
+                height = (sum(s.height for s in size) +
+                          self.line_spacing * (len(text) - 1))
+
+                textsize = Size(width, height)
+            else:
+                textsize = Size(0, 0)
+
+        if scaled:
+            textsize = Size(textsize.width * self.scale,
+                            textsize.height * self.scale)
+
+        return textsize
+
+    @property
+    def lines(self):
+        textsize = self.textsize(self._result, scaled=True)
+
+        _, dy = self.box.get_padding_for(textsize, valign=self.valign,
+                                         padding=self.line_spacing)
+
+        height = dy
+        base_xy = XY(self.box.x1, self.box.y1)
+        for string in self._result:
+            textsize = self.textsize(string, scaled=True)
+            dx, _ = self.box.get_padding_for(textsize, halign=self.halign,
+                                             padding=self.padding)
+
+            if self.adjustBaseline:
+                draw_xy = base_xy.shift(dx, height + textsize.height)
+            else:
+                draw_xy = base_xy.shift(dx, height)
+
+            yield string, draw_xy
+
+            height += textsize.height + self.line_spacing
+
+    @property
+    def outlinebox(self):
+        corners = []
+        for string, xy in self.lines:
+            textsize = self.textsize(string)
+            width = textsize[0] * self.scale
+            height = textsize[1] * self.scale
+
+            if self.adjustBaseline:
+                xy = XY(xy.x, xy.y - textsize[1])
+
+            corners.append(xy)
+            corners.append(XY(xy.x + width, xy.y + height))
+
+        if corners:
+            box = Box(min(p.x for p in corners) - self.padding,
+                      min(p.y for p in corners) - self.line_spacing,
+                      max(p.x for p in corners) + self.padding,
+                      max(p.y for p in corners) + self.line_spacing)
+        else:
+            box = Box(self.box[0], self.box[1], self.box[0], self.box[1])
+
+        return box
+
+    def _lines(self):
+        lines = []
+        measure = 'width'
+        maxwidth, maxheight = self.box.size
+
+        height = 0
+        finished = False
+        for line in splitlabel(self.string):
+            for folded in splittext(self, line, maxwidth, measure):
+                textsize = self.textsize(folded)
+
+                if height + textsize.height + self.line_spacing < maxheight:
+                    lines.append(folded)
+                    height += textsize.height + self.line_spacing
+                elif len(lines) > 0:
+                    lines[-1] = truncate_text(self, lines[-1],
+                                              maxwidth, measure)
+                    finished = True
+                    break
+
+            if finished:
+                break
+
+        return lines
diff --git a/src/blockdiag/imagedraw/utils/__init__.py b/src/blockdiag/imagedraw/utils/__init__.py
new file mode 100644
index 0000000..27af44c
--- /dev/null
+++ b/src/blockdiag/imagedraw/utils/__init__.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+#  Copyright 2011 Takeshi KOMIYA
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import math
+import unicodedata
+from blockdiag.utils import Size
+
+
+def is_zenkaku(char):
+    u"""Detect given character is Japanese ZENKAKU character
+
+        >>> is_zenkaku(u"A")
+        False
+        >>> is_zenkaku(u"あ")
+        True
+    """
+    char_width = unicodedata.east_asian_width(char)
+    return char_width in u"WFA"
+
+
+def zenkaku_len(string):
+    u"""
+    Count Japanese ZENKAKU characters from string
+
+    >>> zenkaku_len(u"abc")
+    0
+    >>> zenkaku_len(u"あいう")
+    3
+    >>> zenkaku_len(u"あいc")
+    2
+    """
+    return len([x for x in string if is_zenkaku(x)])
+
+
+def hankaku_len(string):
+    u"""Count non Japanese ZENKAKU characters from string
+
+        >>> hankaku_len(u"abc")
+        3
+        >>> hankaku_len(u"あいう")
+        0
+        >>> hankaku_len(u"あいc")
+        1
+    """
+    return len([x for x in string if not is_zenkaku(x)])
+
+
+def string_width(string):
+    u"""Measure rendering width of string.
+        Count ZENKAKU-character as 2-point and non ZENKAKU-character as 1-point
+
+        >>> string_width(u"abc")
+        3
+        >>> string_width(u"あいう")
+        6
+        >>> string_width(u"あいc")
+        5
+    """
+    widthmap = {'Na': 1, 'N': 1, 'H': 1, 'W': 2, 'F': 2, 'A': 2}
+    return sum(widthmap[unicodedata.east_asian_width(c)] for c in string)
+
+
+def textsize(string, font):
+    u"""Measure rendering size (width and height) of line.
+        Returned size will not be exactly as rendered text size,
+        Because this method does not use fonts to measure size.
+
+        >>> from blockdiag.utils.fontmap import FontInfo
+        >>> box = [0, 0, 100, 50]
+        >>> font = FontInfo('serif', None, 11)
+        >>> textsize(u"abc", font)
+        Size(width=19, height=11)
+        >>> textsize(u"あいう", font)
+        Size(width=33, height=11)
+        >>> textsize(u"あいc", font)
+        Size(width=29, height=11)
+        >>> font = FontInfo('serif', None, 24)
+        >>> textsize(u"abc", font)
+        Size(width=40, height=24)
+        >>> font = FontInfo('serif', None, 18)
+        >>> textsize(u"あいう", font)
+        Size(width=54, height=18)
+    """
+    width = (zenkaku_len(string) * font.size +
+             hankaku_len(string) * font.size * 0.55)
+
+    return Size(int(math.ceil(width)), font.size)
+
+
+def cached(fn):
+    def func(self, *args, **kwargs):
+        name = fn.__name__
+        key = args + tuple(kwargs.values())
+
+        if name not in self._method_cache:
+            self._method_cache[name] = {}
+
+        if key not in self._method_cache[name]:
+            self._method_cache[name][key] = fn(self, *args, **kwargs)
+
+        return self._method_cache[name][key]
+
+    return func
diff --git a/src/blockdiag/utils/ellipse.py b/src/blockdiag/imagedraw/utils/ellipse.py
similarity index 68%
rename from src/blockdiag/utils/ellipse.py
rename to src/blockdiag/imagedraw/utils/ellipse.py
index 580a2f5..c3d48a8 100644
--- a/src/blockdiag/utils/ellipse.py
+++ b/src/blockdiag/imagedraw/utils/ellipse.py
@@ -20,24 +20,27 @@ DIVISION = 1000.0
 CYCLE = 10
 
 
-def angles(du, a, b, start, end):
+def _angles(du, a, b, start, end):
     phi = (start / 180.0) * math.pi
     while phi <= (end / 180.0) * math.pi:
         yield phi
-        phi += du / math.sqrt((a * math.sin(phi)) ** 2 + \
+        phi += du / math.sqrt((a * math.sin(phi)) ** 2 +
                               (b * math.cos(phi)) ** 2)
 
 
-def coordinate(du, a, b, start, end):
-    for angle in angles(du, a, b, start, end):
+def _coordinates(du, a, b, start, end):
+    for angle in _angles(du, a, b, start, end):
         yield (a * math.cos(angle), b * math.sin(angle))
 
 
-def dots(box, cycle, start=0, end=360):
-    width = box[2] - box[0]
-    height = box[3] - box[1]
-    center = XY(box[0] + width / 2, box[1] + height / 2)
+def endpoints(du, a, b, start, end):
+    pt1 = iter(_coordinates(du, a, b, start, start + 1)).next()
+    pt2 = iter(_coordinates(du, a, b, end, end + 1)).next()
+
+    return [XY(*pt1), XY(*pt2)]
 
+
+def dots(box, cycle, start=0, end=360):
     # calcrate rendering pattern from cycle
     base = 0
     rendered = []
@@ -47,11 +50,11 @@ def dots(box, cycle, start=0, end=360):
             rendered.append(n)
         base += i + j
 
-    a = float(width) / 2
-    b = float(height) / 2
+    a = float(box.width) / 2
+    b = float(box.height) / 2
     du = 1
     _max = sum(cycle) * 2
-    for i, coord in enumerate(coordinate(du, a, b, start, end)):
+    center = box.center
+    for i, coord in enumerate(_coordinates(du, a, b, start, end)):
         if i % _max in rendered:
-            dot = XY(center.x + coord[0], center.y + coord[1])
-            yield dot
+            yield XY(center.x + coord[0], center.y + coord[1])
diff --git a/src/blockdiag/metrics.py b/src/blockdiag/metrics.py
index 0d36920..ac64e62 100644
--- a/src/blockdiag/metrics.py
+++ b/src/blockdiag/metrics.py
@@ -14,11 +14,11 @@
 #  limitations under the License.
 
 import copy
-from elements import DiagramNode
-import noderenderer
-from utils import Box, Size, XY
-from utils.fontmap import FontInfo, FontMap
-from utils.collections import defaultdict, namedtuple
+from blockdiag import noderenderer
+from blockdiag.elements import DiagramNode
+from blockdiag.utils import Box, Size, XY
+from blockdiag.utils.fontmap import FontInfo, FontMap
+from blockdiag.utils.collections import defaultdict
 
 cellsize = 8
 
@@ -42,7 +42,7 @@ class EdgeLines(object):
         else:
             elem = XY(x, y)
 
-        if self.stroking == False:
+        if self.stroking is False:
             self.stroking = True
             polyline = []
             if self.xy:
@@ -73,19 +73,25 @@ class AutoScaler(object):
 
     def __getattr__(self, name):
         ratio = self.scale_ratio
-        attr = getattr(self.subject, name)
+        return self.scale(getattr(self.subject, name), ratio)
 
-        if not callable(attr):
-            return self.scale(attr, ratio)
+    def __getitem__(self, name):
+        ratio = self.scale_ratio
+        return self.scale(self.subject[name], ratio)
+
+    @classmethod
+    def scale(cls, value, ratio):
+        if not callable(value):
+            return cls._scale(value, ratio)
         else:
             def _(*args, **kwargs):
-                ret = attr(*args, **kwargs)
-                return self.scale(ret, ratio)
+                ret = value(*args, **kwargs)
+                return cls._scale(ret, ratio)
 
             return _
 
     @classmethod
-    def scale(cls, value, ratio):
+    def _scale(cls, value, ratio):
         if ratio == 1:
             return value
 
@@ -134,7 +140,7 @@ class DiagramMetrics(object):
     span_height = cellsize * 5
 
     def __init__(self, diagram, **kwargs):
-        self.format = kwargs.get('format')
+        self.drawer = kwargs.get('drawer')
 
         if diagram.node_width is not None:
             self.node_width = diagram.node_width
@@ -190,20 +196,8 @@ class DiagramMetrics(object):
 
         return metrics
 
-    def textsize(self, string, width=65535, font=None):
-        try:
-            if font is None:
-                from utils.TextFolder import TextFolder
-            elif self.format == 'PDF':
-                from utils.PDFTextFolder import PDFTextFolder as TextFolder
-            else:
-                from utils.PILTextFolder import PILTextFolder as TextFolder
-        except ImportError:
-            from utils.TextFolder import TextFolder
-
-        lines = TextFolder((0, 0, width, 65535), string, font)
-        textbox = lines.outlinebox
-        return XY(textbox.width, textbox.height + self.line_spacing)
+    def textsize(self, string, font=None, width=65535):
+        return self.drawer.textsize(string, font, maxwidth=width)
 
     def node(self, node):
         renderer = noderenderer.get(node.shape)
@@ -238,7 +232,12 @@ class DiagramMetrics(object):
         return self.spreadsheet.pagesize(width, height)
 
 
-class SpreadSheetMetrics(object):
+class SubMetrics(object):
+    def __getattr__(self, name):
+        return getattr(self.metrics, name)
+
+
+class SpreadSheetMetrics(SubMetrics):
     def __init__(self, metrics):
         self.metrics = metrics
         self.node_width = defaultdict(lambda: metrics.node_width)
@@ -247,37 +246,41 @@ class SpreadSheetMetrics(object):
         self.span_height = defaultdict(lambda: metrics.span_height)
 
     def set_node_width(self, x, width):
-        if width is not None and 0 < width and \
-           (x not in self.node_width or self.node_width[x] < width):
+        if (width is not None and 0 < width and
+           (x not in self.node_width or self.node_width[x] < width)):
             self.node_width[x] = width
 
     def set_node_height(self, y, height):
-        if height is not None and 0 < height and \
-           (y not in self.node_height or self.node_height[y] < height):
+        if (height is not None and 0 < height and
+           (y not in self.node_height or self.node_height[y] < height)):
             self.node_height[y] = height
 
     def set_span_width(self, x, width):
-        if width is not None and 0 < width and \
-           (x not in self.span_width or self.span_width[x] < width):
+        if (width is not None and 0 < width and
+           (x not in self.span_width or self.span_width[x] < width)):
             self.span_width[x] = width
 
+    def add_span_width(self, x, width):
+        self.span_width[x] += width
+
     def set_span_height(self, y, height):
-        if height is not None and 0 < height and \
-           (y not in self.span_height or self.span_height[y] < height):
+        if (height is not None and 0 < height and
+           (y not in self.span_height or self.span_height[y] < height)):
             self.span_height[y] = height
 
+    def add_span_height(self, y, height):
+        self.span_height[y] += height
+
     def node(self, node, use_padding=True):
-        x, y = node.xy
         x1, y1 = self._node_topleft(node, use_padding)
         x2, y2 = self._node_bottomright(node, use_padding)
 
         return NodeMetrics(self.metrics, x1, y1, x2, y2)
 
     def _node_topleft(self, node, use_padding=True):
-        m = self.metrics
         x, y = node.xy
-        margin = m.page_margin
-        padding = m.page_padding
+        margin = self.page_margin
+        padding = self.page_padding
 
         node_width = sum(self.node_width[i] for i in range(x))
         node_height = sum(self.node_height[i] for i in range(y))
@@ -285,11 +288,13 @@ class SpreadSheetMetrics(object):
         span_height = sum(self.span_height[i] for i in range(y + 1))
 
         if use_padding:
-            xdiff = (self.node_width[x] - (node.width or m.node_width)) / 2
+            width = node.width or self.metrics.node_width
+            xdiff = (self.node_width[x] - width) / 2
             if xdiff < 0:
                 xdiff = 0
 
-            ydiff = (self.node_height[y] - (node.height or m.node_height)) / 2
+            height = node.height or self.metrics.node_height
+            ydiff = (self.node_height[y] - height) / 2
             if ydiff < 0:
                 ydiff = 0
         else:
@@ -302,11 +307,10 @@ class SpreadSheetMetrics(object):
         return XY(x1, y1)
 
     def _node_bottomright(self, node, use_padding=True):
-        m = self.metrics
         x = node.xy.x + node.colwidth - 1
         y = node.xy.y + node.colheight - 1
-        margin = m.page_margin
-        padding = m.page_padding
+        margin = self.page_margin
+        padding = self.page_padding
 
         node_width = sum(self.node_width[i] for i in range(x + 1))
         node_height = sum(self.node_height[i] for i in range(y + 1))
@@ -314,11 +318,13 @@ class SpreadSheetMetrics(object):
         span_height = sum(self.span_height[i] for i in range(y + 1))
 
         if use_padding:
-            xdiff = (self.node_width[x] - (node.width or m.node_width)) / 2
+            width = node.width or self.metrics.node_width
+            xdiff = (self.node_width[x] - width) / 2
             if xdiff < 0:
                 xdiff = 0
 
-            ydiff = (self.node_height[y] - (node.height or m.node_height)) / 2
+            height = node.height or self.metrics.node_height
+            ydiff = (self.node_height[y] - height) / 2
             if ydiff < 0:
                 ydiff = 0
         else:
@@ -343,41 +349,58 @@ class SpreadSheetMetrics(object):
                   y + margin.y + padding[2] + y_span)
 
 
-class NodeMetrics(Box):
+class NodeMetrics(SubMetrics):
     def __init__(self, metrics, x1, y1, x2, y2):
         self.metrics = metrics
-        super(NodeMetrics, self).__init__(x1, y1, x2, y2)
+        self._box = Box(x1, y1, x2, y2)
+
+    def __getattr__(self, name):
+        if hasattr(self._box, name):
+            return getattr(self._box, name)
+        else:
+            return getattr(self.metrics, name)
+
+    def __getitem__(self, key):
+        return self.box[key]
 
     @property
     def box(self):
-        return Box(self.x1, self.y1, self.x2, self.y2)
+        return self._box
 
     @property
     def marginbox(self):
-        return Box(self.x1 - self.metrics.span_width / 8,
-                   self.y1 - self.metrics.span_height / 4,
-                   self.x2 + self.metrics.span_width / 8,
-                   self.y2 + self.metrics.span_height / 4)
+        return Box(self._box.x1 - self.span_width / 8,
+                   self._box.y1 - self.span_height / 4,
+                   self._box.x2 + self.span_width / 8,
+                   self._box.y2 + self.span_height / 4)
 
     @property
     def corebox(self):
-        return Box(self.x1 + self.metrics.node_padding,
-                   self.y1 + self.metrics.node_padding,
-                   self.x2 - self.metrics.node_padding * 2,
-                   self.y2 - self.metrics.node_padding * 2)
+        return Box(self._box.x1 + self.node_padding,
+                   self._box.y1 + self.node_padding,
+                   self._box.x2 - self.node_padding * 2,
+                   self._box.y2 - self.node_padding * 2)
 
     @property
     def grouplabelbox(self):
-        return Box(self.x1, self.y1 - self.metrics.span_height / 2,
-                   self.x2, self.y1)
+        return Box(self._box.x1, self._box.y1 - self.span_height / 2,
+                   self._box.x2, self._box.y1)
 
 
-class EdgeMetrics(object):
+class EdgeMetrics(SubMetrics):
     def __init__(self, edge, metrics):
         self.metrics = metrics
         self.edge = edge
 
     @property
+    def headshapes(self):
+        pass
+
+    @property
+    def _shaft(self):
+        pass
+
+    @property
     def heads(self):
         heads = []
         head1, head2 = self.headshapes
@@ -392,8 +415,8 @@ class EdgeMetrics(object):
 
     def _head(self, node, direct):
         head = []
-        cell = self.metrics.cellsize
-        node = self.metrics.node(node)
+        cell = self.cellsize
+        node = self.node(node)
 
         if direct == 'up':
             xy = node.bottom
@@ -459,7 +482,7 @@ class EdgeMetrics(object):
 
     @property
     def shaft(self):
-        cell = self.metrics.cellsize
+        cell = self.cellsize
         lines = self._shaft
         head1, head2 = self.headshapes
 
@@ -512,18 +535,18 @@ class LandscapeEdgeMetrics(EdgeMetrics):
     @property
     def headshapes(self):
         heads = []
-        dir = self.edge.direction
+        _dir = self.edge.direction
 
         if self.edge.dir in ('back', 'both'):
-            if dir in ('left-up', 'left', 'same',
-                       'right-up', 'right', 'right-down'):
+            if _dir in ('left-up', 'left', 'same',
+                        'right-up', 'right', 'right-down'):
                 heads.append('left')
-            elif dir == 'up':
+            elif _dir == 'up':
                 if self.edge.skipped:
                     heads.append('left')
                 else:
                     heads.append('down')
-            elif dir in ('left-down', 'down'):
+            elif _dir in ('left-down', 'down'):
                 if self.edge.skipped:
                     heads.append('left')
                 else:
@@ -535,11 +558,11 @@ class LandscapeEdgeMetrics(EdgeMetrics):
             heads.append(None)
 
         if self.edge.dir in ('forward', 'both'):
-            if dir in ('right-up', 'right', 'right-down'):
+            if _dir in ('right-up', 'right', 'right-down'):
                 heads.append('right')
-            elif dir == 'up':
+            elif _dir == 'up':
                 heads.append('up')
-            elif dir in ('left-up', 'left', 'left-down', 'down', 'same'):
+            elif _dir in ('left-up', 'left', 'left-down', 'down', 'same'):
                 heads.append('down')
 
             if self.edge.hstyle in ('onemany', 'manymany'):
@@ -551,16 +574,16 @@ class LandscapeEdgeMetrics(EdgeMetrics):
 
     @property
     def _shaft(self):
-        span = XY(self.metrics.span_width, self.metrics.span_height)
-        dir = self.edge.direction
+        span = XY(self.span_width, self.span_height)
+        _dir = self.edge.direction
 
-        node1 = self.metrics.node(self.edge.node1)
-        cell1 = self.metrics.cell(self.edge.node1, use_padding=False)
-        node2 = self.metrics.node(self.edge.node2)
-        cell2 = self.metrics.cell(self.edge.node2, use_padding=False)
+        node1 = self.node(self.edge.node1)
+        cell1 = self.cell(self.edge.node1, use_padding=False)
+        node2 = self.node(self.edge.node2)
+        cell2 = self.cell(self.edge.node2, use_padding=False)
 
         shaft = EdgeLines()
-        if dir == 'right':
+        if _dir == 'right':
             shaft.moveTo(node1.right)
 
             if self.edge.skipped:
@@ -573,7 +596,7 @@ class LandscapeEdgeMetrics(EdgeMetrics):
 
             shaft.lineTo(node2.left)
 
-        elif dir == 'right-up':
+        elif _dir == 'right-up':
             shaft.moveTo(node1.right)
 
             if self.edge.skipped:
@@ -589,7 +612,7 @@ class LandscapeEdgeMetrics(EdgeMetrics):
 
             shaft.lineTo(node2.left)
 
-        elif dir == 'right-down':
+        elif _dir == 'right-down':
             shaft.moveTo(node1.right)
             shaft.lineTo(cell1.right.x + span.x / 2, cell1.right.y)
 
@@ -604,7 +627,7 @@ class LandscapeEdgeMetrics(EdgeMetrics):
 
             shaft.lineTo(node2.left)
 
-        elif dir == 'up':
+        elif _dir == 'up':
             if self.edge.skipped:
                 shaft.moveTo(node1.right)
                 shaft.lineTo(cell1.right.x + span.x / 4, cell1.right.y)
@@ -616,7 +639,7 @@ class LandscapeEdgeMetrics(EdgeMetrics):
 
             shaft.lineTo(node2.bottom)
 
-        elif dir in ('left-up', 'left', 'same'):
+        elif _dir in ('left-up', 'left', 'same'):
             shaft.moveTo(node1.right)
             shaft.lineTo(cell1.right.x + span.x / 4, cell1.right.y)
             shaft.lineTo(cell1.right.x + span.x / 4,
@@ -625,7 +648,7 @@ class LandscapeEdgeMetrics(EdgeMetrics):
                          cell2.top.y - span.y / 2 + span.y / 8)
             shaft.lineTo(node2.top)
 
-        elif dir == 'left-down':
+        elif _dir == 'left-down':
             if self.edge.skipped:
                 shaft.moveTo(node1.right)
                 shaft.lineTo(cell1.right.x + span.x / 2, cell1.right.y)
@@ -640,7 +663,7 @@ class LandscapeEdgeMetrics(EdgeMetrics):
 
             shaft.lineTo(node2.top)
 
-        elif dir == 'down':
+        elif _dir == 'down':
             if self.edge.skipped:
                 shaft.moveTo(node1.right)
                 shaft.lineTo(cell1.right.x + span.x / 2, cell1.right.y)
@@ -657,14 +680,14 @@ class LandscapeEdgeMetrics(EdgeMetrics):
 
     @property
     def labelbox(self):
-        span = XY(self.metrics.span_width, self.metrics.span_height)
-        node = XY(self.metrics.node_width, self.metrics.node_height)
+        span = XY(self.span_width, self.span_height)
+        node = XY(self.node_width, self.node_height)
 
-        dir = self.edge.direction
-        node1 = self.metrics.cell(self.edge.node1, use_padding=False)
-        node2 = self.metrics.cell(self.edge.node2, use_padding=False)
+        _dir = self.edge.direction
+        node1 = self.cell(self.edge.node1, use_padding=False)
+        node2 = self.cell(self.edge.node2, use_padding=False)
 
-        if dir == 'right':
+        if _dir == 'right':
             if self.edge.skipped:
                 box = Box(node1.bottomright.x + span.x,
                           node1.bottomright.y,
@@ -674,15 +697,15 @@ class LandscapeEdgeMetrics(EdgeMetrics):
                 box = Box(node1.topright.x, node1.topright.y - span.y / 8,
                           node2.left.x, node2.left.y - span.y / 8)
 
-        elif dir == 'right-up':
+        elif _dir == 'right-up':
             box = Box(node2.left.x - span.x, node1.top.y - node.y / 2,
                       node2.bottomleft.x, node1.top.y)
 
-        elif dir == 'right-down':
+        elif _dir == 'right-down':
             box = Box(node1.right.x, node2.topleft.y - span.y / 8,
                       node1.right.x + span.x, node2.left.y - span.y / 8)
 
-        elif dir in ('up', 'left-up', 'left', 'same'):
+        elif _dir in ('up', 'left-up', 'left', 'same'):
             if self.edge.node2.xy.y < self.edge.node1.xy.y:
                 box = Box(node1.topright.x - span.x / 2 + span.x / 4,
                           node1.topright.y - span.y / 2,
@@ -694,7 +717,7 @@ class LandscapeEdgeMetrics(EdgeMetrics):
                           node1.topright.x + span.x / 4,
                           node1.topright.y - span.y / 2)
 
-        elif dir in ('left-down', 'down'):
+        elif _dir in ('left-down', 'down'):
             box = Box(node2.top.x + span.x / 4,
                       node2.top.y - span.y,
                       node2.topright.x + span.x / 4,
@@ -711,19 +734,19 @@ class PortraitEdgeMetrics(EdgeMetrics):
     @property
     def headshapes(self):
         heads = []
-        dir = self.edge.direction
+        _dir = self.edge.direction
 
         if self.edge.dir in ('back', 'both'):
-            if dir == 'right':
+            if _dir == 'right':
                 if self.edge.skipped:
                     heads.append('up')
                 else:
                     heads.append('left')
-            elif dir in ('up', 'right-up', 'same'):
+            elif _dir in ('up', 'right-up', 'same'):
                 heads.append('up')
-            elif dir in ('left-up', 'left'):
+            elif _dir in ('left-up', 'left'):
                 heads.append('left')
-            elif dir in ('left-down', 'down', 'right-down'):
+            elif _dir in ('left-down', 'down', 'right-down'):
                 if self.edge.skipped:
                     heads.append('left')
                 else:
@@ -735,14 +758,15 @@ class PortraitEdgeMetrics(EdgeMetrics):
             heads.append(None)
 
         if self.edge.dir in ('forward', 'both'):
-            if dir == 'right':
+            if _dir == 'right':
                 if self.edge.skipped:
                     heads.append('down')
                 else:
                     heads.append('right')
-            elif dir in ('up', 'right-up', 'same'):
+            elif _dir in ('up', 'right-up', 'same'):
                 heads.append('down')
-            elif dir in ('left-up', 'left', 'left-down', 'down', 'right-down'):
+            elif _dir in ('left-up', 'left',
+                          'left-down', 'down', 'right-down'):
                 heads.append('down')
 
             if self.edge.hstyle in ('onemany', 'manymany'):
@@ -754,17 +778,17 @@ class PortraitEdgeMetrics(EdgeMetrics):
 
     @property
     def _shaft(self):
-        span = XY(self.metrics.span_width, self.metrics.span_height)
-        dir = self.edge.direction
+        span = XY(self.span_width, self.span_height)
+        _dir = self.edge.direction
 
-        node1 = self.metrics.node(self.edge.node1)
-        cell1 = self.metrics.cell(self.edge.node1, use_padding=False)
-        node2 = self.metrics.node(self.edge.node2)
-        cell2 = self.metrics.cell(self.edge.node2, use_padding=False)
+        node1 = self.node(self.edge.node1)
+        cell1 = self.cell(self.edge.node1, use_padding=False)
+        node2 = self.node(self.edge.node2)
+        cell2 = self.cell(self.edge.node2, use_padding=False)
 
         shaft = EdgeLines()
-        if dir in ('up', 'right-up', 'same', 'right'):
-            if dir == 'right' and not self.edge.skipped:
+        if _dir in ('up', 'right-up', 'same', 'right'):
+            if _dir == 'right' and not self.edge.skipped:
                 shaft.moveTo(node1.right)
                 shaft.lineTo(node2.left)
             else:
@@ -778,7 +802,7 @@ class PortraitEdgeMetrics(EdgeMetrics):
                              cell2.top.y - span.y / 2 + span.y / 8)
                 shaft.lineTo(node2.top)
 
-        elif dir == 'right-down':
+        elif _dir == 'right-down':
             shaft.moveTo(node1.bottom)
             shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y / 2)
 
@@ -793,7 +817,7 @@ class PortraitEdgeMetrics(EdgeMetrics):
 
             shaft.lineTo(node2.top)
 
-        elif dir in ('left-up', 'left', 'same'):
+        elif _dir in ('left-up', 'left', 'same'):
             shaft.moveTo(node1.right)
             shaft.lineTo(cell1.right.x + span.x / 4, cell1.right.y)
             shaft.lineTo(cell1.right.x + span.x / 4,
@@ -802,7 +826,7 @@ class PortraitEdgeMetrics(EdgeMetrics):
                          cell2.top.y - span.y / 2 + span.y / 8)
             shaft.lineTo(node2.top)
 
-        elif dir == 'left-down':
+        elif _dir == 'left-down':
             shaft.moveTo(node1.bottom)
 
             if self.edge.skipped:
@@ -817,7 +841,7 @@ class PortraitEdgeMetrics(EdgeMetrics):
             shaft.lineTo(cell2.top.x, cell2.top.y - span.y / 2)
             shaft.lineTo(node2.top)
 
-        elif dir == 'down':
+        elif _dir == 'down':
             shaft.moveTo(node1.bottom)
 
             if self.edge.skipped:
@@ -834,13 +858,13 @@ class PortraitEdgeMetrics(EdgeMetrics):
 
     @property
     def labelbox(self):
-        span = XY(self.metrics.span_width, self.metrics.span_height)
+        span = XY(self.span_width, self.span_height)
 
-        dir = self.edge.direction
-        node1 = self.metrics.cell(self.edge.node1, use_padding=False)
-        node2 = self.metrics.cell(self.edge.node2, use_padding=False)
+        _dir = self.edge.direction
+        node1 = self.cell(self.edge.node1, use_padding=False)
+        node2 = self.cell(self.edge.node2, use_padding=False)
 
-        if dir == 'right':
+        if _dir == 'right':
             if self.edge.skipped:
                 box = Box(node1.bottomright.x + span.x,
                           node1.bottomright.y,
@@ -850,15 +874,15 @@ class PortraitEdgeMetrics(EdgeMetrics):
                 box = Box(node1.topright.x, node1.topright.y - span.y / 8,
                           node2.left.x, node2.left.y - span.y / 8)
 
-        elif dir == 'right-up':
+        elif _dir == 'right-up':
             box = Box(node2.left.x - span.x, node2.left.y,
                       node2.bottomleft.x, node2.bottomleft.y)
 
-        elif dir == 'right-down':
+        elif _dir == 'right-down':
             box = Box(node2.topleft.x, node2.topleft.y - span.y / 2,
                       node2.top.x, node2.top.y)
 
-        elif dir in ('up', 'left-up', 'left', 'same'):
+        elif _dir in ('up', 'left-up', 'left', 'same'):
             if self.edge.node2.xy.y < self.edge.node1.xy.y:
                 box = Box(node1.topright.x - span.x / 2 + span.x / 4,
                           node1.topright.y - span.y / 2,
@@ -870,13 +894,13 @@ class PortraitEdgeMetrics(EdgeMetrics):
                           node1.topright.x + span.x / 4,
                           node1.topright.y - span.y / 2)
 
-        elif dir == 'down':
+        elif _dir == 'down':
             box = Box(node2.top.x + span.x / 4,
                       node2.top.y - span.y / 2,
                       node2.topright.x + span.x / 4,
                       node2.topright.y)
 
-        elif dir == 'left-down':
+        elif _dir == 'left-down':
             box = Box(node1.bottomleft.x, node1.bottomleft.y,
                       node1.bottom.x, node1.bottom.y + span.y / 2)
 
@@ -916,11 +940,11 @@ class FlowchartLandscapeEdgeMetrics(LandscapeEdgeMetrics):
     @property
     def _shaft(self):
         if self.edge.direction == 'right-down':
-            span = XY(self.metrics.span_width, self.metrics.span_height)
-            node1 = self.metrics.node(self.edge.node1)
-            cell1 = self.metrics.cell(self.edge.node1, use_padding=False)
-            node2 = self.metrics.node(self.edge.node2)
-            cell2 = self.metrics.cell(self.edge.node2, use_padding=False)
+            span = XY(self.span_width, self.span_height)
+            node1 = self.node(self.edge.node1)
+            cell1 = self.cell(self.edge.node1, use_padding=False)
+            node2 = self.node(self.edge.node2)
+            cell2 = self.cell(self.edge.node2, use_padding=False)
 
             shaft = EdgeLines()
             shaft.moveTo(node1.bottom)
@@ -941,13 +965,11 @@ class FlowchartLandscapeEdgeMetrics(LandscapeEdgeMetrics):
 
     @property
     def labelbox(self):
-        dir = self.edge.direction
-        if dir == 'right':
-            span = XY(self.metrics.span_width, self.metrics.span_height)
-            node1 = self.metrics.node(self.edge.node1)
-            cell1 = self.metrics.cell(self.edge.node1, use_padding=False)
-            node2 = self.metrics.node(self.edge.node2)
-            cell2 = self.metrics.cell(self.edge.node2, use_padding=False)
+        _dir = self.edge.direction
+        if _dir == 'right':
+            span = XY(self.span_width, self.span_height)
+            cell1 = self.cell(self.edge.node1, use_padding=False)
+            cell2 = self.cell(self.edge.node2, use_padding=False)
 
             if self.edge.skipped:
                 box = Box(cell1.bottom.x, cell1.bottom.y,
@@ -991,11 +1013,11 @@ class FlowchartPortraitEdgeMetrics(PortraitEdgeMetrics):
     @property
     def _shaft(self):
         if self.edge.direction == 'right-down':
-            span = XY(self.metrics.span_width, self.metrics.span_height)
-            node1 = self.metrics.node(self.edge.node1)
-            cell1 = self.metrics.cell(self.edge.node1, use_padding=False)
-            node2 = self.metrics.node(self.edge.node2)
-            cell2 = self.metrics.cell(self.edge.node2, use_padding=False)
+            span = XY(self.span_width, self.span_height)
+            node1 = self.node(self.edge.node1)
+            cell1 = self.cell(self.edge.node1, use_padding=False)
+            node2 = self.node(self.edge.node2)
+            cell2 = self.cell(self.edge.node2, use_padding=False)
 
             shaft = EdgeLines()
             shaft.moveTo(node1.right)
@@ -1016,17 +1038,15 @@ class FlowchartPortraitEdgeMetrics(PortraitEdgeMetrics):
 
     @property
     def labelbox(self):
-        dir = self.edge.direction
-        span = XY(self.metrics.span_width, self.metrics.span_height)
-        node1 = self.metrics.node(self.edge.node1)
-        cell1 = self.metrics.cell(self.edge.node1, use_padding=False)
-        node2 = self.metrics.node(self.edge.node2)
-        cell2 = self.metrics.cell(self.edge.node2, use_padding=False)
-
-        if dir == 'down':
+        _dir = self.edge.direction
+        span = XY(self.span_width, self.span_height)
+        cell1 = self.cell(self.edge.node1, use_padding=False)
+        cell2 = self.cell(self.edge.node2, use_padding=False)
+
+        if _dir == 'down':
             box = Box(cell2.topleft.x, cell2.top.y - span.y / 2,
                       cell2.top.x, cell2.top.y)
-        elif dir == 'right':
+        elif _dir == 'right':
             if self.edge.skipped:
                 box = Box(cell1.bottom.x, cell1.bottom.y,
                           cell1.bottomright.x,
diff --git a/src/blockdiag/noderenderer/__init__.py b/src/blockdiag/noderenderer/__init__.py
index 0165c21..96fcdfd 100644
--- a/src/blockdiag/noderenderer/__init__.py
+++ b/src/blockdiag/noderenderer/__init__.py
@@ -78,7 +78,7 @@ class NodeShape(object):
             self.textbox = Box(self.iconbox[2], m.top.y,
                                m.bottomright.x, m.bottomright.y)
 
-    def render(self, drawer, format, **kwargs):
+    def render(self, drawer, _format, **kwargs):
         if self.node.stacked and not kwargs.get('stacked'):
             node = self.node.duplicate()
             node.label = ""
@@ -88,23 +88,23 @@ class NodeShape(object):
                 r = self.metrics.original_metrics.cellsize / 2 * i
                 metrics = self.metrics.shift(r, r)
 
-                self.__class__(node, metrics).render(drawer, format,
+                self.__class__(node, metrics).render(drawer, _format,
                                                      stacked=True, **kwargs)
 
-        if hasattr(self, 'render_vector_shape') and format == 'SVG':
-            self.render_vector_shape(drawer, format, **kwargs)
+        if hasattr(self, 'render_vector_shape') and _format == 'SVG':
+            self.render_vector_shape(drawer, _format, **kwargs)
         else:
-            self.render_shape(drawer, format, **kwargs)
+            self.render_shape(drawer, _format, **kwargs)
 
         self.render_icon(drawer, **kwargs)
         self.render_label(drawer, **kwargs)
         self.render_number_badge(drawer, **kwargs)
 
     def render_icon(self, drawer, **kwargs):
-        if self.node.icon != None and kwargs.get('shadow') != True:
-            drawer.loadImage(self.node.icon, self.iconbox)
+        if self.node.icon is not None and kwargs.get('shadow') is False:
+            drawer.image(self.iconbox, self.node.icon)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         pass
 
     def render_label(self, drawer, **kwargs):
@@ -113,16 +113,17 @@ class NodeShape(object):
             drawer.textarea(self.textbox, self.node.label, font,
                             rotate=self.node.rotate,
                             fill=self.node.textcolor, halign=self.textalign,
-                            line_spacing=self.metrics.line_spacing)
+                            line_spacing=self.metrics.line_spacing,
+                            orientation=self.node.label_orientation)
 
     def render_number_badge(self, drawer, **kwargs):
-        if self.node.numbered != None and kwargs.get('shadow') != True:
+        if self.node.numbered is not None and kwargs.get('shadow') is None:
             badgeFill = kwargs.get('badgeFill')
 
             xy = self.metrics.cell(self.node).topleft
             r = self.metrics.cellsize * 3 / 2
 
-            box = (xy.x - r, xy.y - r, xy.x + r, xy.y + r)
+            box = Box(xy.x - r, xy.y - r, xy.x + r, xy.y + r)
             font = self.metrics.font_for(self.node)
             drawer.ellipse(box, outline=self.node.linecolor, fill=badgeFill)
             drawer.textarea(box, self.node.numbered, font,
diff --git a/src/blockdiag/noderenderer/actor.py b/src/blockdiag/noderenderer/actor.py
index d1ada53..bd5c520 100644
--- a/src/blockdiag/noderenderer/actor.py
+++ b/src/blockdiag/noderenderer/actor.py
@@ -72,7 +72,7 @@ class Actor(NodeShape):
                 XY(bodyC.x - neckWidth, bodyC.y - armWidth),  # left arm end
                 XY(bodyC.x - neckWidth, bodyC.y - r * 2)]
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # FIXME: Actor does not support
@@ -83,7 +83,10 @@ class Actor(NodeShape):
         body = self.body_part()
         if kwargs.get('shadow'):
             body = self.shift_shadow(body)
-            drawer.polygon(body, fill=fill, filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.polygon(body, fill=fill, filter='transp-blur')
+            else:
+                drawer.polygon(body, fill=fill)
         else:
             drawer.polygon(body, fill=self.node.color,
                            outline=self.node.linecolor, style=self.node.style)
@@ -92,8 +95,11 @@ class Actor(NodeShape):
         head = self.head_part()
         if kwargs.get('shadow'):
             head = self.shift_shadow(head)
-            drawer.ellipse(head, fill=fill, outline=self.node.linecolor,
-                           filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.ellipse(head, fill=fill, outline=self.node.linecolor,
+                               filter='transp-blur')
+            else:
+                drawer.ellipse(head, fill=fill, outline=self.node.linecolor)
         else:
             drawer.ellipse(head, fill=self.node.color,
                            outline=self.node.linecolor, style=self.node.style)
diff --git a/src/blockdiag/noderenderer/beginpoint.py b/src/blockdiag/noderenderer/beginpoint.py
index 09a2643..3229918 100644
--- a/src/blockdiag/noderenderer/beginpoint.py
+++ b/src/blockdiag/noderenderer/beginpoint.py
@@ -33,7 +33,7 @@ class BeginPoint(NodeShape):
                            XY(self.center.x, self.center.y + self.radius),
                            XY(self.center.x - self.radius, self.center.y)]
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # draw outline
@@ -42,7 +42,11 @@ class BeginPoint(NodeShape):
                   self.center.x + r, self.center.y + r)
         if kwargs.get('shadow'):
             box = self.shift_shadow(box)
-            drawer.ellipse(box, fill=fill, outline=fill, filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.ellipse(box, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.ellipse(box, fill=fill, outline=fill)
         else:
             if self.node.color == self.node.basecolor:
                 color = self.node.linecolor
diff --git a/src/blockdiag/noderenderer/box.py b/src/blockdiag/noderenderer/box.py
index 7ce04d6..4f2abb3 100644
--- a/src/blockdiag/noderenderer/box.py
+++ b/src/blockdiag/noderenderer/box.py
@@ -18,19 +18,22 @@ from blockdiag.noderenderer import install_renderer
 
 
 class Box(NodeShape):
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # draw outline
         box = self.metrics.cell(self.node).box
         if kwargs.get('shadow'):
             box = self.shift_shadow(box)
-            drawer.rectangle(box, fill=fill, outline=fill,
-                             filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.rectangle(box, fill=fill, outline=fill,
+                                 filter='transp-blur')
+            else:
+                drawer.rectangle(box, fill=fill, outline=fill)
         elif self.node.background:
             drawer.rectangle(box, fill=self.node.color,
                              outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
             drawer.rectangle(box, outline=self.node.linecolor,
                              style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/circle.py b/src/blockdiag/noderenderer/circle.py
index c82934a..90afd9b 100644
--- a/src/blockdiag/noderenderer/circle.py
+++ b/src/blockdiag/noderenderer/circle.py
@@ -17,17 +17,21 @@ class Circle(NodeShape):
                            XY(pt.x - r, pt.y)]  # left
         self.textbox = Box(pt.x - r, pt.y - r, pt.x + r, pt.y + r)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # draw outline
         if kwargs.get('shadow'):
             box = self.shift_shadow(self.textbox)
-            drawer.ellipse(box, fill=fill, outline=fill, filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.ellipse(box, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.ellipse(box, fill=fill, outline=fill)
         elif self.node.background:
             drawer.ellipse(self.textbox, fill=self.node.color,
                            outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
             drawer.ellipse(self.textbox, fill="none",
                            outline=self.node.linecolor, style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/cloud.py b/src/blockdiag/noderenderer/cloud.py
index 5720c60..9e22bd7 100644
--- a/src/blockdiag/noderenderer/cloud.py
+++ b/src/blockdiag/noderenderer/cloud.py
@@ -29,14 +29,14 @@ class Cloud(NodeShape):
         self.textbox = Box(pt.x + rx * 2, pt.y + ry,
                            pt.x + rx * 11, pt.y + ry * 4)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         # draw background
-        self.render_shape_background(drawer, format, **kwargs)
+        self.render_shape_background(drawer, **kwargs)
 
         if not kwargs.get('shadow') and self.node.background:
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
 
-    def render_shape_background(self, drawer, format, **kwargs):
+    def render_shape_background(self, drawer, **kwargs):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
@@ -64,8 +64,11 @@ class Cloud(NodeShape):
         for e in ellipses:
             if kwargs.get('shadow'):
                 e = self.shift_shadow(e)
-                drawer.ellipse(e, fill=fill, outline=fill,
-                               filter='transp-blur')
+                if kwargs.get('style') == 'blur':
+                    drawer.ellipse(e, fill=fill, outline=fill,
+                                   filter='transp-blur')
+                else:
+                    drawer.ellipse(e, fill=fill, outline=fill)
             else:
                 drawer.ellipse(e, fill=self.node.color,
                                outline=self.node.linecolor,
@@ -78,13 +81,16 @@ class Cloud(NodeShape):
         for rect in rects:
             if kwargs.get('shadow'):
                 rect = self.shift_shadow(rect)
-                drawer.rectangle(rect, fill=fill, outline=fill,
-                                 filter='transp-blur')
+                if kwargs.get('style') == 'blur':
+                    drawer.rectangle(rect, fill=fill, outline=fill,
+                                     filter='transp-blur')
+                else:
+                    drawer.rectangle(rect, fill=fill, outline=fill)
             else:
                 drawer.rectangle(rect, fill=self.node.color,
                                  outline=self.node.color)
 
-    def render_vector_shape(self, drawer, format, **kwargs):
+    def render_vector_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # create pathdata
@@ -108,8 +114,11 @@ class Cloud(NodeShape):
 
         # draw outline
         if kwargs.get('shadow'):
-            drawer.path(path, fill=fill, outline=fill,
-                        filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.path(path, fill=fill, outline=fill,
+                            filter='transp-blur')
+            else:
+                drawer.path(path, fill=fill, outline=fill)
         elif self.node.background:
             drawer.path(path, fill=self.node.color, outline=self.node.color)
             drawer.loadImage(self.node.background, self.textbox)
diff --git a/src/blockdiag/noderenderer/diamond.py b/src/blockdiag/noderenderer/diamond.py
index 1f46ea6..32c8179 100644
--- a/src/blockdiag/noderenderer/diamond.py
+++ b/src/blockdiag/noderenderer/diamond.py
@@ -34,18 +34,21 @@ class Diamond(NodeShape):
                            (self.connectors[1].x + self.connectors[2].x) / 2,
                            (self.connectors[1].y + self.connectors[2].y) / 2)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # draw outline
         if kwargs.get('shadow'):
             diamond = self.shift_shadow(self.connectors)
-            drawer.polygon(diamond, fill=fill, outline=fill,
-                             filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.polygon(diamond, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.polygon(diamond, fill=fill, outline=fill)
         elif self.node.background:
             drawer.polygon(self.connectors, fill=self.node.color,
                            outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
             drawer.polygon(self.connectors, fill="none",
                            outline=self.node.linecolor, style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/dots.py b/src/blockdiag/noderenderer/dots.py
index d57d4d9..d5bf313 100644
--- a/src/blockdiag/noderenderer/dots.py
+++ b/src/blockdiag/noderenderer/dots.py
@@ -15,14 +15,14 @@
 
 from blockdiag.noderenderer import NodeShape
 from blockdiag.noderenderer import install_renderer
-from blockdiag.utils import XY
+from blockdiag.utils import Box, XY
 
 
 class Dots(NodeShape):
     def render_label(self, drawer, **kwargs):
         pass
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         if kwargs.get('shadow'):
             return
 
@@ -44,7 +44,7 @@ class Dots(NodeShape):
 
         r = m.cellsize / 2
         for dot in dots:
-            box = (dot.x - r, dot.y - r, dot.x + r, dot.y + r)
+            box = Box(dot.x - r, dot.y - r, dot.x + r, dot.y + r)
             drawer.ellipse(box, fill=self.node.linecolor,
                            outline=self.node.linecolor)
 
diff --git a/src/blockdiag/noderenderer/ellipse.py b/src/blockdiag/noderenderer/ellipse.py
index baba42d..caecd7b 100644
--- a/src/blockdiag/noderenderer/ellipse.py
+++ b/src/blockdiag/noderenderer/ellipse.py
@@ -26,19 +26,22 @@ class Ellipse(NodeShape):
         box = metrics.cell(node).box
         self.textbox = Box(box[0] + r, box[1] + r, box[2] - r, box[3] - r)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # draw outline
         box = self.metrics.cell(self.node).box
         if kwargs.get('shadow'):
             box = self.shift_shadow(box)
-            drawer.ellipse(box, fill=fill, outline=fill,
-                           filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.ellipse(box, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.ellipse(box, fill=fill, outline=fill)
         elif self.node.background:
             drawer.ellipse(box, fill=self.node.color,
                            outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
             drawer.ellipse(box, fill="none",
                            outline=self.node.linecolor, style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/endpoint.py b/src/blockdiag/noderenderer/endpoint.py
index 68dec65..ac1eca3 100644
--- a/src/blockdiag/noderenderer/endpoint.py
+++ b/src/blockdiag/noderenderer/endpoint.py
@@ -33,7 +33,7 @@ class EndPoint(NodeShape):
                            XY(self.center.x, self.center.y + self.radius),
                            XY(self.center.x - self.radius, self.center.y)]
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # draw outer circle
@@ -42,7 +42,11 @@ class EndPoint(NodeShape):
                   self.center.x + r, self.center.y + r)
         if kwargs.get('shadow'):
             box = self.shift_shadow(box)
-            drawer.ellipse(box, fill=fill, outline=fill, filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.ellipse(box, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.ellipse(box, fill=fill, outline=fill)
         else:
             drawer.ellipse(box, fill='white', outline=self.node.linecolor,
                            style=self.node.style)
diff --git a/src/blockdiag/noderenderer/flowchart/database.py b/src/blockdiag/noderenderer/flowchart/database.py
index dc3174e..404b93c 100644
--- a/src/blockdiag/noderenderer/flowchart/database.py
+++ b/src/blockdiag/noderenderer/flowchart/database.py
@@ -28,15 +28,15 @@ class Database(NodeShape):
         self.textbox = Box(m.topleft.x, m.topleft.y + r * 3 / 2,
                            m.bottomright.x, m.bottomright.y - r / 2)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         # draw background
-        self.render_shape_background(drawer, format, **kwargs)
+        self.render_shape_background(drawer, **kwargs)
 
         # draw background image
         if self.node.background:
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
 
-    def render_shape_background(self, drawer, format, **kwargs):
+    def render_shape_background(self, drawer, **kwargs):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
@@ -46,8 +46,11 @@ class Database(NodeShape):
         ellipse = Box(box[0], box[3] - r * 2, box[2], box[3])
         if kwargs.get('shadow'):
             ellipse = self.shift_shadow(ellipse)
-            drawer.ellipse(ellipse, fill=fill, outline=fill,
-                           filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.ellipse(ellipse, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.ellipse(ellipse, fill=fill, outline=fill)
         else:
             drawer.ellipse(ellipse, fill=self.node.color,
                            outline=self.node.linecolor, style=self.node.style)
@@ -55,8 +58,11 @@ class Database(NodeShape):
         rect = Box(box[0], box[1] + r, box[2], box[3] - r)
         if kwargs.get('shadow'):
             rect = self.shift_shadow(rect)
-            drawer.rectangle(rect, fill=fill, outline=fill,
-                             filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.rectangle(rect, fill=fill, outline=fill,
+                                 filter='transp-blur')
+            else:
+                drawer.rectangle(rect, fill=fill, outline=fill)
         else:
             drawer.rectangle(rect, fill=self.node.color,
                              outline=self.node.color)
@@ -64,8 +70,11 @@ class Database(NodeShape):
         ellipse = Box(box[0], box[1], box[2], box[1] + r * 2)
         if kwargs.get('shadow'):
             ellipse = self.shift_shadow(ellipse)
-            drawer.ellipse(ellipse, fill=fill, outline=fill,
-                           filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.ellipse(ellipse, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.ellipse(ellipse, fill=fill, outline=fill)
         else:
             drawer.ellipse(ellipse, fill=self.node.color,
                            outline=self.node.linecolor, style=self.node.style)
@@ -78,7 +87,7 @@ class Database(NodeShape):
                 drawer.line(line, fill=self.node.linecolor,
                             style=self.node.style)
 
-    def render_vector_shape(self, drawer, format, **kwargs):
+    def render_vector_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
@@ -97,12 +106,15 @@ class Database(NodeShape):
 
         # draw outline
         if kwargs.get('shadow'):
-            drawer.path(path, fill=fill, outline=fill,
-                        filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.path(path, fill=fill, outline=fill,
+                            filter='transp-blur')
+            else:
+                drawer.path(path, fill=fill, outline=fill)
         elif self.node.background:
             drawer.path(path, fill=self.node.color,
                         outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
             drawer.path(path, fill="none",
                         outline=self.node.linecolor, style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/flowchart/input.py b/src/blockdiag/noderenderer/flowchart/input.py
index 5e9b0ce..8092dd7 100644
--- a/src/blockdiag/noderenderer/flowchart/input.py
+++ b/src/blockdiag/noderenderer/flowchart/input.py
@@ -28,7 +28,7 @@ class Input(NodeShape):
         self.textbox = Box(m.topleft.x + r, m.topleft.y,
                            m.bottomright.x - r, m.bottomright.y)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
@@ -43,12 +43,15 @@ class Input(NodeShape):
         # draw outline
         if kwargs.get('shadow'):
             shape = self.shift_shadow(shape)
-            drawer.polygon(shape, fill=fill, outline=fill,
-                           filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.polygon(shape, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.polygon(shape, fill=fill, outline=fill)
         elif self.node.background:
             drawer.polygon(shape, fill=self.node.color,
-                             outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+                           outline=self.node.color)
+            drawer.image(self.textbox, self.node.background)
             drawer.polygon(shape, fill="none",
                            outline=self.node.linecolor, style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/flowchart/loopin.py b/src/blockdiag/noderenderer/flowchart/loopin.py
index 106318e..5ab7324 100644
--- a/src/blockdiag/noderenderer/flowchart/loopin.py
+++ b/src/blockdiag/noderenderer/flowchart/loopin.py
@@ -28,7 +28,7 @@ class LoopIn(NodeShape):
         self.textbox = Box(m.topleft.x, m.topleft.y + ydiff,
                            m.bottomright.x, m.bottomright.y)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
@@ -46,12 +46,15 @@ class LoopIn(NodeShape):
         # draw outline
         if kwargs.get('shadow'):
             shape = self.shift_shadow(shape)
-            drawer.polygon(shape, fill=fill, outline=fill,
-                           filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.polygon(shape, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.polygon(shape, fill=fill, outline=fill)
         elif self.node.background:
             drawer.polygon(shape, fill=self.node.color,
                            outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
             drawer.polygon(shape, fill="none",
                            outline=self.node.linecolor, style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/flowchart/loopout.py b/src/blockdiag/noderenderer/flowchart/loopout.py
index edfe072..e534e36 100644
--- a/src/blockdiag/noderenderer/flowchart/loopout.py
+++ b/src/blockdiag/noderenderer/flowchart/loopout.py
@@ -28,7 +28,7 @@ class LoopOut(NodeShape):
         self.textbox = Box(m.topleft.x, m.topleft.y,
                            m.bottomright.x, m.bottomright.y - ydiff)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
@@ -36,22 +36,25 @@ class LoopOut(NodeShape):
         ydiff = self.metrics.node_height / 4
 
         shape = [XY(m.topleft.x, m.topleft.y),
-                XY(m.topright.x, m.topright.y),
-                XY(m.bottomright.x, m.bottomright.y - ydiff),
-                XY(m.bottomright.x - xdiff, m.bottomright.y),
-                XY(m.bottomleft.x + xdiff, m.bottomleft.y),
-                XY(m.bottomleft.x, m.bottomleft.y - ydiff),
-                XY(m.topleft.x, m.topleft.y)]
+                 XY(m.topright.x, m.topright.y),
+                 XY(m.bottomright.x, m.bottomright.y - ydiff),
+                 XY(m.bottomright.x - xdiff, m.bottomright.y),
+                 XY(m.bottomleft.x + xdiff, m.bottomleft.y),
+                 XY(m.bottomleft.x, m.bottomleft.y - ydiff),
+                 XY(m.topleft.x, m.topleft.y)]
 
         # draw outline
         if kwargs.get('shadow'):
             shape = self.shift_shadow(shape)
-            drawer.polygon(shape, fill=fill, outline=fill,
-                           filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.polygon(shape, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.polygon(shape, fill=fill, outline=fill)
         elif self.node.background:
             drawer.polygon(shape, fill=self.node.color,
-                             outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+                           outline=self.node.color)
+            drawer.image(self.textbox, self.node.background)
             drawer.polygon(shape, fill="none",
                            outline=self.node.linecolor, style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/flowchart/terminator.py b/src/blockdiag/noderenderer/flowchart/terminator.py
index 015e555..c8ba449 100644
--- a/src/blockdiag/noderenderer/flowchart/terminator.py
+++ b/src/blockdiag/noderenderer/flowchart/terminator.py
@@ -28,15 +28,15 @@ class Terminator(NodeShape):
         self.textbox = Box(m.topleft.x + r, m.topleft.y,
                            m.bottomright.x - r, m.bottomright.y)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         # draw background
-        self.render_shape_background(drawer, format, **kwargs)
+        self.render_shape_background(drawer, **kwargs)
 
         # draw outline
         if not kwargs.get('shadow') and self.node.background:
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
 
-    def render_shape_background(self, drawer, format, **kwargs):
+    def render_shape_background(self, drawer, **kwargs):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
@@ -49,8 +49,11 @@ class Terminator(NodeShape):
         for e in ellipses:
             if kwargs.get('shadow'):
                 e = self.shift_shadow(e)
-                drawer.ellipse(e, fill=fill, outline=fill,
-                               filter='transp-blur')
+                if kwargs.get('style') == 'blur':
+                    drawer.ellipse(e, fill=fill, outline=fill,
+                                   filter='transp-blur')
+                else:
+                    drawer.ellipse(e, fill=fill, outline=fill)
             else:
                 drawer.ellipse(e, fill=self.node.color,
                                outline=self.node.linecolor,
@@ -59,8 +62,11 @@ class Terminator(NodeShape):
         rect = Box(box[0] + r, box[1], box[2] - r, box[3])
         if kwargs.get('shadow'):
             rect = self.shift_shadow(rect)
-            drawer.rectangle(rect, fill=fill, outline=fill,
-                             filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.rectangle(rect, fill=fill, outline=fill,
+                                 filter='transp-blur')
+            else:
+                drawer.rectangle(rect, fill=fill, outline=fill)
         else:
             drawer.rectangle(rect, fill=self.node.color,
                              outline=self.node.color)
@@ -72,7 +78,7 @@ class Terminator(NodeShape):
                 drawer.line(line, fill=self.node.linecolor,
                             style=self.node.style)
 
-    def render_vector_shape(self, drawer, format, **kwargs):
+    def render_vector_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # create pathdata
@@ -93,8 +99,11 @@ class Terminator(NodeShape):
 
         # draw outline
         if kwargs.get('shadow'):
-            drawer.path(path, fill=fill, outline=fill,
-                        filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.path(path, fill=fill, outline=fill,
+                            filter='transp-blur')
+            else:
+                drawer.path(path, fill=fill, outline=fill)
         elif self.node.background:
             drawer.path(path, fill=self.node.color, outline=self.node.color)
             drawer.loadImage(self.node.background, self.textbox)
diff --git a/src/blockdiag/noderenderer/mail.py b/src/blockdiag/noderenderer/mail.py
index d5f84ae..b7935c3 100644
--- a/src/blockdiag/noderenderer/mail.py
+++ b/src/blockdiag/noderenderer/mail.py
@@ -27,7 +27,7 @@ class Mail(NodeShape):
         self.textbox = Box(m.topleft.x, m.topleft.y + r,
                            m.bottomright.x, m.bottomright.y)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
@@ -37,12 +37,15 @@ class Mail(NodeShape):
         box = self.metrics.cell(self.node).box
         if kwargs.get('shadow'):
             box = self.shift_shadow(box)
-            drawer.rectangle(box, fill=fill, outline=fill,
-                             filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.rectangle(box, fill=fill, outline=fill,
+                                 filter='transp-blur')
+            else:
+                drawer.rectangle(box, fill=fill, outline=fill)
         elif self.node.background:
             drawer.rectangle(box, fill=self.node.color,
                              outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
             drawer.rectangle(box, outline=self.node.linecolor,
                              style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/minidiamond.py b/src/blockdiag/noderenderer/minidiamond.py
index 9ef04b1..39014d1 100644
--- a/src/blockdiag/noderenderer/minidiamond.py
+++ b/src/blockdiag/noderenderer/minidiamond.py
@@ -33,14 +33,17 @@ class MiniDiamond(NodeShape):
         self.textbox = Box(m.top.x, m.top.y, m.right.x, m.right.y)
         self.textalign = 'left'
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # draw outline
         if kwargs.get('shadow'):
             diamond = self.shift_shadow(self.connectors)
-            drawer.polygon(diamond, fill=fill, outline=fill,
-                             filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.polygon(diamond, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.polygon(diamond, fill=fill, outline=fill)
         else:
             drawer.polygon(self.connectors, fill=self.node.color,
                            outline=self.node.linecolor, style=self.node.style)
diff --git a/src/blockdiag/noderenderer/none.py b/src/blockdiag/noderenderer/none.py
index e8a9824..81ef91d 100644
--- a/src/blockdiag/noderenderer/none.py
+++ b/src/blockdiag/noderenderer/none.py
@@ -27,7 +27,7 @@ class NoneShape(NodeShape):
     def render_label(self, drawer, **kwargs):
         pass
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         pass
 
 
diff --git a/src/blockdiag/noderenderer/note.py b/src/blockdiag/noderenderer/note.py
index b0eea3d..5cfc15a 100644
--- a/src/blockdiag/noderenderer/note.py
+++ b/src/blockdiag/noderenderer/note.py
@@ -19,7 +19,7 @@ from blockdiag.utils import XY
 
 
 class Note(NodeShape):
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
@@ -33,12 +33,15 @@ class Note(NodeShape):
         # draw outline
         if kwargs.get('shadow'):
             note = self.shift_shadow(note)
-            drawer.polygon(note, fill=fill, outline=fill,
-                           filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.polygon(note, fill=fill, outline=fill,
+                               filter='transp-blur')
+            else:
+                drawer.polygon(note, fill=fill, outline=fill)
         elif self.node.background:
             drawer.polygon(note, fill=self.node.color,
                            outline=self.node.color)
-            drawer.loadImage(self.node.background, box)
+            drawer.image(box, self.node.background)
             drawer.polygon(note, fill="none",
                            outline=self.node.linecolor, style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/roundedbox.py b/src/blockdiag/noderenderer/roundedbox.py
index fcc50cd..54fe322 100644
--- a/src/blockdiag/noderenderer/roundedbox.py
+++ b/src/blockdiag/noderenderer/roundedbox.py
@@ -20,19 +20,19 @@ from blockdiag.imagedraw.simplesvg import pathdata
 
 
 class RoundedBox(NodeShape):
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         # draw background
-        self.render_shape_background(drawer, format, **kwargs)
+        self.render_shape_background(drawer, **kwargs)
 
         # draw outline
         box = self.metrics.cell(self.node).box
         if not kwargs.get('shadow'):
             if self.node.background:
-                drawer.loadImage(self.node.background, box)
+                drawer.image(box, self.node.background)
 
-            self.render_shape_outline(drawer, format, **kwargs)
+            self.render_shape_outline(drawer, **kwargs)
 
-    def render_shape_outline(self, drawer, format, **kwargs):
+    def render_shape_outline(self, drawer, **kwargs):
         m = self.metrics.cell(self.node)
         r = self.metrics.cellsize
         box = m.box
@@ -44,15 +44,16 @@ class RoundedBox(NodeShape):
         for line in lines:
             drawer.line(line, fill=self.node.linecolor, style=self.node.style)
 
-        arcs = [((box[0], box[1], box[0] + r * 2, box[1] + r * 2), 180, 270),
-                ((box[2] - r * 2, box[1], box[2], box[1] + r * 2), 270, 360),
-                ((box[2] - r * 2, box[3] - r * 2, box[2], box[3]), 0, 90),
-                ((box[0], box[3] - r * 2, box[0] + r * 2, box[3]), 90, 180)]
+        r2 = r * 2
+        arcs = [(Box(box[0], box[1], box[0] + r2, box[1] + r2), 180, 270),
+                (Box(box[2] - r2, box[1], box[2], box[1] + r2), 270, 360),
+                (Box(box[2] - r2, box[3] - r2, box[2], box[3]), 0, 90),
+                (Box(box[0], box[3] - r2, box[0] + r2, box[3]), 90, 180)]
         for arc in arcs:
             drawer.arc(arc[0], arc[1], arc[2],
                        fill=self.node.linecolor, style=self.node.style)
 
-    def render_shape_background(self, drawer, format, **kwargs):
+    def render_shape_background(self, drawer, **kwargs):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
@@ -67,8 +68,11 @@ class RoundedBox(NodeShape):
         for e in ellipses:
             if kwargs.get('shadow'):
                 e = self.shift_shadow(e)
-                drawer.ellipse(e, fill=fill, outline=fill,
-                               filter='transp-blur')
+                if kwargs.get('style') == 'blur':
+                    drawer.ellipse(e, fill=fill, outline=fill,
+                                   filter='transp-blur')
+                else:
+                    drawer.ellipse(e, fill=fill, outline=fill)
             else:
                 drawer.ellipse(e, fill=self.node.color,
                                outline=self.node.color)
@@ -78,13 +82,16 @@ class RoundedBox(NodeShape):
         for rect in rects:
             if kwargs.get('shadow'):
                 rect = self.shift_shadow(rect)
-                drawer.rectangle(rect, fill=fill, outline=fill,
-                                 filter='transp-blur')
+                if kwargs.get('style') == 'blur':
+                    drawer.rectangle(rect, fill=fill, outline=fill,
+                                     filter='transp-blur')
+                else:
+                    drawer.rectangle(rect, fill=fill, outline=fill)
             else:
                 drawer.rectangle(rect, fill=self.node.color,
                                  outline=self.node.color)
 
-    def render_vector_shape(self, drawer, format, **kwargs):
+    def render_vector_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # create pathdata
@@ -106,8 +113,11 @@ class RoundedBox(NodeShape):
 
         # draw outline
         if kwargs.get('shadow'):
-            drawer.path(path, fill=fill, outline=fill,
-                        filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.path(path, fill=fill, outline=fill,
+                            filter='transp-blur')
+            else:
+                drawer.path(path, fill=fill, outline=fill)
         elif self.node.background:
             drawer.path(path, fill=self.node.color, outline=self.node.color)
             drawer.loadImage(self.node.background, self.textbox)
diff --git a/src/blockdiag/noderenderer/square.py b/src/blockdiag/noderenderer/square.py
index 52d6972..6019949 100644
--- a/src/blockdiag/noderenderer/square.py
+++ b/src/blockdiag/noderenderer/square.py
@@ -17,19 +17,21 @@ class Square(NodeShape):
                            XY(pt.x - r, pt.y)]  # left
         self.textbox = Box(pt.x - r, pt.y - r, pt.x + r, pt.y + r)
 
-    def render_shape(self, drawer, format, **kwargs):
-        outline = kwargs.get('outline')
+    def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
 
         # draw outline
         if kwargs.get('shadow'):
             box = self.shift_shadow(self.textbox)
-            drawer.rectangle(box, fill=fill, outline=fill,
-                             filter='transp-blur')
+            if kwargs.get('style') == 'blur':
+                drawer.rectangle(box, fill=fill, outline=fill,
+                                 filter='transp-blur')
+            else:
+                drawer.rectangle(box, fill=fill, outline=fill)
         elif self.node.background:
             drawer.rectangle(self.textbox, fill=self.node.color,
                              outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
             drawer.rectangle(self.textbox, fill="none",
                              outline=self.node.linecolor,
                              style=self.node.style)
diff --git a/src/blockdiag/noderenderer/textbox.py b/src/blockdiag/noderenderer/textbox.py
index 7e86674..9ff5ccb 100644
--- a/src/blockdiag/noderenderer/textbox.py
+++ b/src/blockdiag/noderenderer/textbox.py
@@ -38,9 +38,9 @@ class TextBox(NodeShape):
         if self.node.icon:
             self.connectors[3] = XY(self.iconbox[0], pt.y)
 
-    def render_shape(self, drawer, format, **kwargs):
+    def render_shape(self, drawer, _, **kwargs):
         if not kwargs.get('shadow') and self.node.background:
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
 
 
 def setup(self):
diff --git a/src/blockdiag/parser.py b/src/blockdiag/parser.py
index e9d57d0..3adb1e9 100644
--- a/src/blockdiag/parser.py
+++ b/src/blockdiag/parser.py
@@ -39,9 +39,9 @@ import codecs
 from re import MULTILINE, DOTALL
 from funcparserlib.lexer import make_tokenizer, Token, LexerError
 from funcparserlib.parser import (some, a, maybe, many, finished, skip,
-    oneplus, forward_decl, NoParseError)
+                                  forward_decl)
 
-from utils.collections import namedtuple
+from blockdiag.utils.collections import namedtuple
 
 ENCODING = 'utf-8'
 
@@ -60,7 +60,7 @@ class ParseException(Exception):
     pass
 
 
-def tokenize(str):
+def tokenize(string):
     'str -> Sequence(Token)'
     specs = [
         ('Comment', (r'/\*(.|[\r\n])*?\*/', MULTILINE)),
@@ -75,7 +75,7 @@ def tokenize(str):
     ]
     useless = ['Comment', 'NL', 'Space']
     t = make_tokenizer(specs)
-    return [x for x in t(str) if x.type not in useless]
+    return [x for x in t(string) if x.type not in useless]
 
 
 def parse(seq):
@@ -87,30 +87,41 @@ def parse(seq):
     n = lambda s: a(Token('Name', s)) >> tokval
     op = lambda s: a(Token('Op', s)) >> tokval
     op_ = lambda s: skip(op(s))
-    id = some(lambda t:
-        t.type in ['Name', 'Number', 'String']).named('id') >> tokval
+    _id = some(lambda t:
+               t.type in ['Name', 'Number', 'String']).named('id') >> tokval
     make_nodes = lambda args: Statements([Node(n, args[-1]) for n in args[0]])
     make_graph_attr = lambda args: DefAttrs(u'graph', [Attr(*args)])
     make_edge = lambda x, x2, xs, attrs: Edge([x, x2] + xs, attrs)
 
-    node_id = id  # + maybe(port)
+    #
+    # parts of syntax
+    #
+    node_id = _id  # + maybe(port)
     node_list = (
         node_id +
         many(op_(',') + node_id)
         >> node_flatten)
     a_list = (
-        id +
-        maybe(op_('=') + id) +
+        _id +
+        maybe(op_('=') + _id) +
         skip(maybe(op(',')))
         >> unarg(Attr))
     attr_list = (
         many(op_('[') + many(a_list) + op_(']'))
         >> flatten)
-    graph_attr = id + op_('=') + id >> make_graph_attr
+    graph_attr = _id + op_('=') + _id >> make_graph_attr
+
+    #  nodes statements::
+    #     A;
+    #     B [attr = value, attr = value];
+    #     C, D [attr = value, attr = value];
+    #
     multi_node_stmt = node_list + attr_list >> make_nodes
-    # We use a forward_decl becaue of circular definitions like (stmt_list ->
-    # stmt -> group -> stmt_list)
-    group = forward_decl()
+
+    #  edge statements::
+    #     A -> B;
+    #     A <- B;
+    #
     edge_rhs = (op('->') | op('--') | op('<-') | op('<->') |
                 op('>-') | op('-<') | op('>-<')) + node_list
     edge_stmt = (
@@ -119,18 +130,33 @@ def parse(seq):
         many(edge_rhs) +
         attr_list
         >> unarg(make_edge))
+
+    #  class statements::
+    #     class red [color = red];
+    #
     class_stmt = (
         skip(n('class')) +
         node_id +
         attr_list
         >> unarg(AttrClass))
+
+    #  plugin statements::
+    #     plugin attributes [name = Name];
+    #
     plugin_stmt = (
         skip(n('plugin')) +
         node_id +
         attr_list
         >> unarg(AttrPlugin))
+
+    #  group statements::
+    #     group {
+    #        A;
+    #     }
+    #
+    group = forward_decl()
     stmt = (
-          edge_stmt
+        edge_stmt
         | class_stmt
         | plugin_stmt
         | group
@@ -140,14 +166,18 @@ def parse(seq):
     stmt_list = many(stmt + skip(maybe(op(';'))))
     group.define(
         skip(n('group')) +
-        maybe(id) +
+        maybe(_id) +
         op_('{') +
         stmt_list +
         op_('}')
         >> unarg(SubGraph))
+
+    #
+    # graph
+    #
     graph = (
         maybe(n('diagram') | n('blockdiag')) +
-        maybe(id) +
+        maybe(_id) +
         op_('{') +
         stmt_list +
         op_('}')
@@ -187,5 +217,5 @@ def parse_string(string):
 
 
 def parse_file(path):
-    input = codecs.open(path, 'r', 'utf-8').read()
-    return parse_string(input)
+    code = codecs.open(path, 'r', 'utf-8').read()
+    return parse_string(code)
diff --git a/src/blockdiag/tests/diagrams/diagram_attributes.diag b/src/blockdiag/tests/diagrams/diagram_attributes.diag
index b5f20d9..2dae457 100644
--- a/src/blockdiag/tests/diagrams/diagram_attributes.diag
+++ b/src/blockdiag/tests/diagrams/diagram_attributes.diag
@@ -9,6 +9,7 @@ blockdiag {
   default_group_color = blue
   default_linecolor = gray
   default_textcolor = green
+  default_label_orientation = vertical
 
   A -> B;
   group {
diff --git a/src/blockdiag/tests/diagrams/node_attribute.diag b/src/blockdiag/tests/diagrams/node_attribute.diag
index 8a8e946..ee9af22 100644
--- a/src/blockdiag/tests/diagrams/node_attribute.diag
+++ b/src/blockdiag/tests/diagrams/node_attribute.diag
@@ -8,4 +8,5 @@ diagram {
   G [stacked];
   H [fontsize = 16];
   I [linecolor = red];
+  J [label="Hello", label_orientation=vertical];
 }
diff --git a/src/blockdiag/tests/diagrams/node_shape_with_none_shadow.diag b/src/blockdiag/tests/diagrams/node_shape_with_none_shadow.diag
new file mode 100644
index 0000000..59f48e7
--- /dev/null
+++ b/src/blockdiag/tests/diagrams/node_shape_with_none_shadow.diag
@@ -0,0 +1,31 @@
+{
+  shadow_style = "none";
+
+  A [shape = "box"];
+  B [shape = "roundedbox"];
+  C [shape = "diamond"];
+  D [shape = "ellipse"];
+  E [shape = "note"];
+  F [shape = "cloud"];
+  G [shape = "mail"];
+  H [shape = "beginpoint"];
+  I [shape = "endpoint"];
+  J [shape = "minidiamond"];
+
+  K [shape = "flowchart.condition"];
+  L [shape = "flowchart.database"];
+  M [shape = "flowchart.input"];
+  N [shape = "flowchart.loopin"];
+  O [shape = "flowchart.loopout"];
+
+  P [shape = "actor"];
+  Q [shape = "flowchart.terminator"];
+  R [shape = "textbox"];
+  S [shape = "dots"];
+  T [shape = "none"];
+
+  U [shape = "square"];
+  V [shape = "circle"];
+
+  Z;
+}
diff --git a/src/blockdiag/tests/diagrams/node_shape_with_solid_shadow.diag b/src/blockdiag/tests/diagrams/node_shape_with_solid_shadow.diag
new file mode 100644
index 0000000..b5514d4
--- /dev/null
+++ b/src/blockdiag/tests/diagrams/node_shape_with_solid_shadow.diag
@@ -0,0 +1,31 @@
+{
+  shadow_style = "solid";
+
+  A [shape = "box"];
+  B [shape = "roundedbox"];
+  C [shape = "diamond"];
+  D [shape = "ellipse"];
+  E [shape = "note"];
+  F [shape = "cloud"];
+  G [shape = "mail"];
+  H [shape = "beginpoint"];
+  I [shape = "endpoint"];
+  J [shape = "minidiamond"];
+
+  K [shape = "flowchart.condition"];
+  L [shape = "flowchart.database"];
+  M [shape = "flowchart.input"];
+  N [shape = "flowchart.loopin"];
+  O [shape = "flowchart.loopout"];
+
+  P [shape = "actor"];
+  Q [shape = "flowchart.terminator"];
+  R [shape = "textbox"];
+  S [shape = "dots"];
+  T [shape = "none"];
+
+  U [shape = "square"];
+  V [shape = "circle"];
+
+  Z;
+}
diff --git a/src/blockdiag/tests/test_boot_params.py b/src/blockdiag/tests/test_boot_params.py
index 0b92330..e0c2c2b 100644
--- a/src/blockdiag/tests/test_boot_params.py
+++ b/src/blockdiag/tests/test_boot_params.py
@@ -4,7 +4,8 @@ import os
 import sys
 import tempfile
 import unittest2
-from utils import argv_wrapper, assertRaises
+from blockdiag.tests.utils import argv_wrapper, assertRaises, with_pdf
+
 import blockdiag
 from blockdiag.command import BlockdiagOptions
 from blockdiag.utils.bootstrap import detectfont
@@ -15,7 +16,7 @@ class TestBootParams(unittest2.TestCase):
         self.parser = BlockdiagOptions(blockdiag)
 
     @argv_wrapper
-    def test_type_option(self):
+    def test_type_option_svg(self):
         sys.argv = ['', '-Tsvg', 'input.diag']
         options = self.parser.parse()
         self.assertEqual(options.output, 'input.svg')
@@ -32,10 +33,15 @@ class TestBootParams(unittest2.TestCase):
         options = self.parser.parse()
         self.assertEqual(options.output, 'input.test.svg')
 
+    @argv_wrapper
+    def test_type_option_png(self):
         sys.argv = ['', '-Tpng', 'input.diag']
         options = self.parser.parse()
         self.assertEqual(options.output, 'input.png')
 
+    @with_pdf
+    @argv_wrapper
+    def test_type_option_pdf(self):
         sys.argv = ['', '-Tpdf', 'input.diag']
         options = self.parser.parse()
         self.assertEqual(options.output, 'input.pdf')
@@ -47,17 +53,39 @@ class TestBootParams(unittest2.TestCase):
         self.parser.parse()
 
     @argv_wrapper
-    def test_separate_option(self):
+    def test_separate_option_svg(self):
         sys.argv = ['', '-Tsvg', '--separate', 'input.diag']
         self.parser.parse()
 
+    @argv_wrapper
+    def test_separate_option_png(self):
         sys.argv = ['', '-Tpng', '--separate', 'input.diag']
         self.parser.parse()
 
+    @with_pdf
+    @argv_wrapper
+    def test_separate_option_pdf(self):
         sys.argv = ['', '-Tpdf', '--separate', 'input.diag']
         self.parser.parse()
 
     @argv_wrapper
+    def test_svg_ignore_pil_option(self):
+        sys.argv = ['', '-Tsvg', '--ignore-pil', 'input.diag']
+        self.parser.parse()
+
+    @assertRaises(RuntimeError)
+    @argv_wrapper
+    def test_png_ignore_pil_option(self):
+        sys.argv = ['', '-Tpng', '--ignore-pil', 'input.diag']
+        self.parser.parse()
+
+    @assertRaises(RuntimeError)
+    @argv_wrapper
+    def test_pdf_ignore_pil_option(self):
+        sys.argv = ['', '-Tpdf', '--ignore-pil', 'input.diag']
+        self.parser.parse()
+
+    @argv_wrapper
     def test_svg_nodoctype_option(self):
         sys.argv = ['', '-Tsvg', '--nodoctype', 'input.diag']
         self.parser.parse()
@@ -144,6 +172,19 @@ class TestBootParams(unittest2.TestCase):
         finally:
             os.unlink(tmp[1])
 
+    @argv_wrapper
+    def test_exist_font_config_option(self):
+        try:
+            tmp = tempfile.mkstemp()
+
+            sys.argv = ['', '-f', tmp[1], 'input.diag']
+            options = self.parser.parse()
+            self.assertEqual(options.font, [tmp[1]])
+            fontpath = detectfont(options)
+            self.assertEqual(fontpath, tmp[1])
+        finally:
+            os.unlink(tmp[1])
+
     @assertRaises(RuntimeError)
     @argv_wrapper
     def test_not_exist_font_config_option(self):
@@ -160,6 +201,36 @@ class TestBootParams(unittest2.TestCase):
         detectfont(options)
 
     @argv_wrapper
+    def test_no_size_option(self):
+        sys.argv = ['', 'input.diag']
+        options = self.parser.parse()
+        self.assertEqual(None, options.size)
+
+    @argv_wrapper
+    def test_size_option(self):
+        sys.argv = ['', '--size', '480x360', 'input.diag']
+        options = self.parser.parse()
+        self.assertEqual([480, 360], options.size)
+
+    @assertRaises(RuntimeError)
+    @argv_wrapper
+    def test_invalid_size_option1(self):
+        sys.argv = ['', '--size', '480-360', 'input.diag']
+        self.parser.parse()
+
+    @assertRaises(RuntimeError)
+    @argv_wrapper
+    def test_invalid_size_option2(self):
+        sys.argv = ['', '--size', '480', 'input.diag']
+        self.parser.parse()
+
+    @assertRaises(RuntimeError)
+    @argv_wrapper
+    def test_invalid_size_option3(self):
+        sys.argv = ['', '--size', 'foobar', 'input.diag']
+        self.parser.parse()
+
+    @argv_wrapper
     def test_auto_font_detection(self):
         sys.argv = ['', 'input.diag']
         options = self.parser.parse()
diff --git a/src/blockdiag/tests/test_builder.py b/src/blockdiag/tests/test_builder.py
index d96942a..cefe4bf 100644
--- a/src/blockdiag/tests/test_builder.py
+++ b/src/blockdiag/tests/test_builder.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from nose.tools import eq_
-from utils import __build_diagram, __validate_node_attributes
+from blockdiag.tests.utils import __build_diagram, __validate_node_attributes
 
 
 def test_diagram_attributes():
diff --git a/src/blockdiag/tests/test_builder_edge.py b/src/blockdiag/tests/test_builder_edge.py
index fe94146..d1bd5b8 100644
--- a/src/blockdiag/tests/test_builder_edge.py
+++ b/src/blockdiag/tests/test_builder_edge.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 
-from nose.tools import eq_
-from utils import stderr_wrapper, __build_diagram, __validate_node_attributes
+from blockdiag.tests.utils import (stderr_wrapper, __build_diagram,
+                                   __validate_node_attributes)
 
 
 def test_single_edge_diagram():
@@ -46,7 +46,7 @@ def test_edge_attribute():
         if edge.node1.id == 'D':
             assert edge.dir == 'none'
             assert edge.color == (0, 0, 0)
-            assert edge.thick == None
+            assert edge.thick is None
         elif edge.node1.id == 'F':
             assert edge.dir == 'forward'
             assert edge.color == (0, 0, 0)
@@ -54,7 +54,7 @@ def test_edge_attribute():
         else:
             assert edge.dir == 'forward'
             assert edge.color == (255, 0, 0)  # red
-            assert edge.thick == None
+            assert edge.thick is None
 
     positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
                  'D': (0, 1), 'E': (1, 1), 'F': (0, 2), 'G': (1, 2)}
diff --git a/src/blockdiag/tests/test_builder_errors.py b/src/blockdiag/tests/test_builder_errors.py
index 4364943..0e1e6b5 100644
--- a/src/blockdiag/tests/test_builder_errors.py
+++ b/src/blockdiag/tests/test_builder_errors.py
@@ -1,100 +1,101 @@
 # -*- coding: utf-8 -*-
 
-from blockdiag.parser import *
 from nose.tools import raises
-from utils import __build_diagram
+from blockdiag.tests.utils import __build_diagram
+
+from blockdiag.parser import ParseException
 
 
 @raises(AttributeError)
 def test_unknown_diagram_default_shape_diagram():
-    diagram = __build_diagram('errors/unknown_diagram_default_shape.diag')
+    __build_diagram('errors/unknown_diagram_default_shape.diag')
 
 
 @raises(AttributeError)
 def test_unknown_diagram_edge_layout_diagram():
-    diagram = __build_diagram('errors/unknown_diagram_edge_layout.diag')
+    __build_diagram('errors/unknown_diagram_edge_layout.diag')
 
 
 @raises(AttributeError)
 def test_unknown_diagram_orientation_diagram():
-    diagram = __build_diagram('errors/unknown_diagram_orientation.diag')
+    __build_diagram('errors/unknown_diagram_orientation.diag')
 
 
 @raises(AttributeError)
 def test_unknown_node_shape_diagram():
-    diagram = __build_diagram('errors/unknown_node_shape.diag')
+    __build_diagram('errors/unknown_node_shape.diag')
 
 
 @raises(AttributeError)
 def test_unknown_node_attribute_diagram():
-    diagram = __build_diagram('errors/unknown_node_attribute.diag')
+    __build_diagram('errors/unknown_node_attribute.diag')
 
 
 @raises(AttributeError)
 def test_unknown_node_style_diagram():
-    diagram = __build_diagram('errors/unknown_node_style.diag')
+    __build_diagram('errors/unknown_node_style.diag')
 
 
 @raises(AttributeError)
 def test_unknown_node_class_diagram():
-    diagram = __build_diagram('errors/unknown_node_class.diag')
+    __build_diagram('errors/unknown_node_class.diag')
 
 
 @raises(AttributeError)
 def test_unknown_edge_dir_diagram():
-    diagram = __build_diagram('errors/unknown_edge_dir.diag')
+    __build_diagram('errors/unknown_edge_dir.diag')
 
 
 @raises(AttributeError)
 def test_unknown_edge_style_diagram():
-    diagram = __build_diagram('errors/unknown_edge_style.diag')
+    __build_diagram('errors/unknown_edge_style.diag')
 
 
 @raises(AttributeError)
 def test_unknown_edge_hstyle_diagram():
-    diagram = __build_diagram('errors/unknown_edge_hstyle.diag')
+    __build_diagram('errors/unknown_edge_hstyle.diag')
 
 
 @raises(AttributeError)
 def test_unknown_edge_class_diagram():
-    diagram = __build_diagram('errors/unknown_edge_class.diag')
+    __build_diagram('errors/unknown_edge_class.diag')
 
 
 @raises(AttributeError)
 def test_unknown_group_shape_diagram():
-    diagram = __build_diagram('errors/unknown_group_shape.diag')
+    __build_diagram('errors/unknown_group_shape.diag')
 
 
 @raises(AttributeError)
 def test_unknown_group_class_diagram():
-    diagram = __build_diagram('errors/unknown_group_class.diag')
+    __build_diagram('errors/unknown_group_class.diag')
 
 
 @raises(AttributeError)
 def test_unknown_group_orientation_diagram():
-    diagram = __build_diagram('errors/unknown_group_orientation.diag')
+    __build_diagram('errors/unknown_group_orientation.diag')
 
 
 @raises(RuntimeError)
 def test_belongs_to_two_groups_diagram():
-    diagram = __build_diagram('errors/belongs_to_two_groups.diag')
+    __build_diagram('errors/belongs_to_two_groups.diag')
 
 
 @raises(AttributeError)
 def test_unknown_plugin_diagram():
-    diagram = __build_diagram('errors/unknown_plugin.diag')
+    __build_diagram('errors/unknown_plugin.diag')
 
 
 @raises(ParseException)
 def test_node_follows_group_diagram():
-    diagram = __build_diagram('errors/node_follows_group.diag')
+    __build_diagram('errors/node_follows_group.diag')
 
 
 @raises(ParseException)
 def test_group_follows_node_diagram():
-    diagram = __build_diagram('errors/group_follows_node.diag')
+    __build_diagram('errors/group_follows_node.diag')
 
 
 @raises(ParseException)
 def test_lexer_error_diagram():
-    diagram = __build_diagram('errors/lexer_error.diag')
+    __build_diagram('errors/lexer_error.diag')
diff --git a/src/blockdiag/tests/test_builder_group.py b/src/blockdiag/tests/test_builder_group.py
index 8817125..efea8b5 100644
--- a/src/blockdiag/tests/test_builder_group.py
+++ b/src/blockdiag/tests/test_builder_group.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-from nose.tools import eq_
-from utils import __build_diagram, __validate_node_attributes
+from blockdiag.tests.utils import __build_diagram, __validate_node_attributes
 
 
 def test_nested_groups_diagram():
@@ -36,7 +35,7 @@ def test_simple_group_diagram():
 
 def test_group_declare_as_node_attribute_diagram():
     positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
-                  'D': (2, 1), 'E': (2, 2), 'Z': (0, 3)}
+                 'D': (2, 1), 'E': (2, 2), 'Z': (0, 3)}
     __validate_node_attributes('group_declare_as_node_attribute.diag',
                                xy=positions)
 
diff --git a/src/blockdiag/tests/test_builder_node.py b/src/blockdiag/tests/test_builder_node.py
index 69482fb..67a2814 100644
--- a/src/blockdiag/tests/test_builder_node.py
+++ b/src/blockdiag/tests/test_builder_node.py
@@ -1,8 +1,8 @@
 # -*- coding: utf-8 -*-
 
-from nose.tools import eq_
+from blockdiag.tests.utils import __build_diagram, __validate_node_attributes
+
 from blockdiag.utils.collections import defaultdict
-from utils import __build_diagram, __validate_node_attributes
 
 
 def test_single_node_diagram():
@@ -65,10 +65,11 @@ def test_multiple_node_relation_diagram():
 def test_node_attribute():
     labels = {'A': 'B', 'B': 'double quoted', 'C': 'single quoted',
               'D': '\'"double" quoted\'', 'E': '"\'single\' quoted"',
-              'F': 'F', 'G': 'G', 'H': 'H', 'I': 'I'}
+              'F': 'F', 'G': 'G', 'H': 'H', 'I': 'I', 'J': 'Hello'}
     colors = {'A': (255, 0, 0), 'B': (255, 255, 255), 'C': (255, 0, 0),
               'D': (255, 0, 0), 'E': (255, 0, 0), 'F': (255, 255, 255),
-              'G': (255, 255, 255), 'H': (255, 255, 255), 'I': (255, 255, 255)}
+              'G': (255, 255, 255), 'H': (255, 255, 255), 'I': (255, 255, 255),
+              'J': (255, 255, 255)}
     textcolors = defaultdict(lambda: (0, 0, 0))
     textcolors['F'] = (255, 0, 0)
     numbered = defaultdict(lambda: None)
@@ -79,11 +80,14 @@ def test_node_attribute():
     fontsize['H'] = 16
     linecolors = defaultdict(lambda: (0, 0, 0))
     linecolors['I'] = (255, 0, 0)
+    orientations = defaultdict(lambda: 'horizontal')
+    orientations['J'] = 'vertical'
 
     __validate_node_attributes('node_attribute.diag', label=labels,
                                color=colors, textcolor=textcolors,
                                numbered=numbered, stacked=stacked,
-                               fontsize=fontsize, linecolor=linecolors)
+                               fontsize=fontsize, linecolor=linecolors,
+                               label_orientation=orientations)
 
 
 def test_node_height_diagram():
@@ -122,7 +126,7 @@ def test_plugin_autoclass_diagram():
               'C': (255, 255, 255)}
 
     __validate_node_attributes('plugin_autoclass.diag', xy=positions,
-                                style=styles, color=colors)
+                               style=styles, color=colors)
 
 
 def test_plugin_attributes_diagram():
diff --git a/src/blockdiag/tests/test_builder_separate.py b/src/blockdiag/tests/test_builder_separate.py
index 2165695..ced9322 100644
--- a/src/blockdiag/tests/test_builder_separate.py
+++ b/src/blockdiag/tests/test_builder_separate.py
@@ -1,8 +1,7 @@
 # -*- coding: utf-8 -*-
 
-import tempfile
-from blockdiag.builder import *
-from blockdiag.elements import *
+from blockdiag.builder import SeparateDiagramBuilder
+from blockdiag.elements import DiagramNode
 from blockdiag.parser import parse_string
 
 
@@ -11,8 +10,8 @@ def __build_diagram(filename):
     testdir = os.path.dirname(__file__)
     pathname = "%s/diagrams/%s" % (testdir, filename)
 
-    str = open(pathname).read()
-    tree = parse_string(str)
+    code = open(pathname).read()
+    tree = parse_string(code)
     return SeparateDiagramBuilder.build(tree)
 
 
diff --git a/src/blockdiag/tests/test_generate_diagram.py b/src/blockdiag/tests/test_generate_diagram.py
index fe3c2a4..0e4fba7 100644
--- a/src/blockdiag/tests/test_generate_diagram.py
+++ b/src/blockdiag/tests/test_generate_diagram.py
@@ -4,10 +4,11 @@ import os
 import sys
 import re
 import tempfile
+from blockdiag.tests.utils import argv_wrapper, stderr_wrapper
+from blockdiag.tests.utils import with_pil, supported_pil, with_pdf
+
 import blockdiag
 import blockdiag.command
-from utils import *
-from blockdiag.elements import *
 
 
 def get_fontpath():
@@ -29,7 +30,7 @@ def extra_case(func):
 
 @argv_wrapper
 @stderr_wrapper
-def __build_diagram(filename, format, *args):
+def __build_diagram(filename, _format, args):
     testdir = os.path.dirname(__file__)
     diagpath = "%s/diagrams/%s" % (testdir, filename)
     fontpath = get_fontpath()
@@ -39,9 +40,12 @@ def __build_diagram(filename, format, *args):
         tmpfile = tempfile.mkstemp(dir=tmpdir)
         os.close(tmpfile[0])
 
-        sys.argv = ['blockdiag.py', '-T', format, '-o', tmpfile[1], diagpath]
+        sys.argv = ['blockdiag.py', '-T', _format, '-o', tmpfile[1], diagpath]
         if args:
-            sys.argv += args
+            if isinstance(args[0], (list, tuple)):
+                sys.argv += args[0]
+            else:
+                sys.argv += args
         if os.path.exists(fontpath):
             sys.argv += ['-f', fontpath]
 
@@ -50,8 +54,8 @@ def __build_diagram(filename, format, *args):
         if re.search('ERROR', sys.stderr.getvalue()):
             raise RuntimeError(sys.stderr.getvalue())
     finally:
-        for file in os.listdir(tmpdir):
-            os.unlink(tmpdir + "/" + file)
+        for filename in os.listdir(tmpdir):
+            os.unlink(tmpdir + "/" + filename)
         os.rmdir(tmpdir)
 
 
@@ -66,36 +70,39 @@ def diagram_files():
 
 
 def test_generator_svg():
-    for testcase in generator_core('svg'):
+    args = []
+    if not supported_pil():
+        args.append('--ignore-pil')
+
+    for testcase in generator_core('svg', args):
         yield testcase
 
 
+ at with_pil
 @extra_case
 def test_generator_png():
     for testcase in generator_core('png'):
         yield testcase
 
 
+ at with_pdf
 @extra_case
 def test_generator_pdf():
-    try:
-        import reportlab.pdfgen.canvas
-        for testcase in generator_core('pdf'):
-            yield testcase
-    except ImportError:
-        sys.stderr.write("Skip testing about pdf exporting.\n")
-        pass
+    for testcase in generator_core('pdf'):
+        yield testcase
 
 
-def generator_core(format):
+def generator_core(_format, *args):
     for diagram in diagram_files():
-        yield __build_diagram, diagram, format
+        yield __build_diagram, diagram, _format, args
 
         if re.search('separate', diagram):
-            yield __build_diagram, diagram, format, '--separate'
+            _args = list(args) + ['--separate']
+            yield __build_diagram, diagram, _format, _args
 
-        if format == 'png':
-            yield __build_diagram, diagram, format, '--antialias'
+        if _format == 'png':
+            _args = list(args) + ['--antialias']
+            yield __build_diagram, diagram, _format, _args
 
 
 @extra_case
@@ -124,6 +131,8 @@ def svg_includes_source_code_tag_test():
         sys.argv = ['blockdiag.py', '-T', 'SVG', '-o', tmpfile[1], diagpath]
         if os.path.exists(fontpath):
             sys.argv += ['-f', fontpath]
+        if not supported_pil():
+            sys.argv += ['--ignore-pil']
 
         blockdiag.command.main()
 
@@ -140,6 +149,6 @@ def svg_includes_source_code_tag_test():
         embeded = re.sub('\s+', ' ', desc.text)
         assert source_code == embeded
     finally:
-        for file in os.listdir(tmpdir):
-            os.unlink(tmpdir + "/" + file)
+        for filename in os.listdir(tmpdir):
+            os.unlink(tmpdir + "/" + filename)
         os.rmdir(tmpdir)
diff --git a/src/blockdiag/tests/test_imagedraw_textfolder.py b/src/blockdiag/tests/test_imagedraw_textfolder.py
new file mode 100644
index 0000000..a7dd075
--- /dev/null
+++ b/src/blockdiag/tests/test_imagedraw_textfolder.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+
+import unittest2
+from blockdiag.imagedraw.textfolder import splitlabel
+from blockdiag.imagedraw.textfolder import splittext
+from blockdiag.imagedraw.textfolder import truncate_text
+from blockdiag.utils import Size
+
+
+CHAR_WIDTH = 14
+CHAR_HEIGHT = 10
+
+
+class Metrics(object):
+    def textsize(self, text):
+        length = len(text)
+        return Size(CHAR_WIDTH * length, CHAR_HEIGHT)
+
+
+class TestTextFolder(unittest2.TestCase):
+    def test_splitlabel(self):
+        # single line text
+        text = "abc"
+        self.assertItemsEqual(['abc'], splitlabel(text))
+
+        # text include \n (as char a.k.a. \x5c)
+        text = "abc\ndef"
+        self.assertItemsEqual(['abc', 'def'], splitlabel(text))
+
+        # text include \n (as mac yensign a.k.a. \xa5)
+        text = "abc\xa5ndef"
+        self.assertItemsEqual(['abc', 'def'], splitlabel(text))
+
+        # text includes \n (as text)
+        text = "abc\\ndef"
+        self.assertItemsEqual(['abc', 'def'], splitlabel(text))
+
+        # text includes escaped \n
+        text = "abc\\\\ndef"
+        self.assertItemsEqual(['abc\\ndef'], splitlabel(text))
+
+        # text includes escaped \n (\x5c and mac yensign mixed)
+        text = u"abc\xa5\\ndef"
+        self.assertItemsEqual(['abc\\ndef'], splitlabel(text))
+
+        # text include \n and spaces
+        text = " abc \n def "
+        self.assertItemsEqual(['abc', 'def'], splitlabel(text))
+
+        # text starts empty line
+        text = " \nabc\ndef"
+        self.assertItemsEqual(['abc', 'def'], splitlabel(text))
+
+        # text starts empty line with \n (as text)
+        text = " \\nabc\\ndef"
+        self.assertItemsEqual(['', 'abc', 'def'], splitlabel(text))
+
+    def test_splittext_width(self):
+        metrics = Metrics()
+
+        # just fit text
+        text = "abc"
+        ret = splittext(metrics, text, CHAR_WIDTH * 3)
+        self.assertEqual(['abc'], ret)
+
+        # text should be folded (once)
+        text = "abcdef"
+        ret = splittext(metrics, text, CHAR_WIDTH * 3)
+        self.assertEqual(['abc', 'def'], ret)
+
+        # text should be folded (twice)
+        text = "abcdefghi"
+        ret = splittext(metrics, text, CHAR_WIDTH * 3)
+        self.assertEqual(['abc', 'def', 'ghi'], ret)
+
+        # empty text
+        text = ""
+        ret = splittext(metrics, text, CHAR_WIDTH * 3)
+        self.assertEqual([' '], ret)
+
+    def test_truncate_text(self):
+        metrics = Metrics()
+
+        # truncated
+        text = "abcdef"
+        ret = truncate_text(metrics, text, CHAR_WIDTH * 8)
+        self.assertEqual("abcd ...", ret)
+
+        # truncated
+        text = "abcdef"
+        ret = truncate_text(metrics, text, CHAR_WIDTH * 5)
+        self.assertEqual("a ...", ret)
+
+        # not truncated (too short)
+        text = "abcdef"
+        ret = truncate_text(metrics, text, CHAR_WIDTH * 4)
+        self.assertEqual("abcdef", ret)
diff --git a/src/blockdiag/tests/test_parser.py b/src/blockdiag/tests/test_parser.py
index 5175378..8676eb3 100644
--- a/src/blockdiag/tests/test_parser.py
+++ b/src/blockdiag/tests/test_parser.py
@@ -1,64 +1,65 @@
 # -*- coding: utf-8 -*-
 
-from blockdiag.parser import *
+from blockdiag.parser import parse_string, ParseException
+from blockdiag.parser import Graph, SubGraph, Statements, Node, Edge
 from nose.tools import raises
 
 
 def test_parser_basic():
     # basic digram
-    str = """
-          diagram test {
-             A -> B -> C, D;
-          }
-          """
+    code = """
+           diagram test {
+              A -> B -> C, D;
+           }
+           """
 
-    tree = parse_string(str)
+    tree = parse_string(code)
     assert isinstance(tree, Graph)
 
 
 def test_parser_without_diagram_id():
-    str = """
-          diagram {
-             A -> B -> C, D;
-          }
-          """
-    tree = parse_string(str)
+    code = """
+           diagram {
+              A -> B -> C, D;
+           }
+           """
+    tree = parse_string(code)
     assert isinstance(tree, Graph)
 
-    str = """
-          {
-             A -> B -> C, D;
-          }
-          """
-    tree = parse_string(str)
+    code = """
+           {
+              A -> B -> C, D;
+           }
+           """
+    tree = parse_string(code)
     assert isinstance(tree, Graph)
 
 
 def test_parser_empty_diagram():
-    str = """
-          diagram {
-          }
-          """
-    tree = parse_string(str)
+    code = """
+           diagram {
+           }
+           """
+    tree = parse_string(code)
     assert isinstance(tree, Graph)
 
-    str = """
-          {
-          }
-          """
-    tree = parse_string(str)
+    code = """
+           {
+           }
+           """
+    tree = parse_string(code)
     assert isinstance(tree, Graph)
 
 
 def test_parser_diagram_includes_nodes():
-    str = """
-          diagram {
-            A;
-            B [label = "foobar"];
-            C [color = "red"];
-          }
-          """
-    tree = parse_string(str)
+    code = """
+           diagram {
+             A;
+             B [label = "foobar"];
+             C [color = "red"];
+           }
+           """
+    tree = parse_string(code)
     assert isinstance(tree, Graph)
     assert len(tree.stmts) == 3
     assert isinstance(tree.stmts[0], Statements)
@@ -70,24 +71,24 @@ def test_parser_diagram_includes_nodes():
 
 
 def test_parser_diagram_includes_edges():
-    str = """
-          diagram {
-            A -> B -> C;
-          }
-          """
-    tree = parse_string(str)
+    code = """
+           diagram {
+             A -> B -> C;
+           }
+           """
+    tree = parse_string(code)
     assert isinstance(tree, Graph)
     print tree.stmts
     assert len(tree.stmts) == 1
     assert isinstance(tree.stmts[0], Edge)
 
-    str = """
-          diagram {
-            A -> B -> C [style = dotted];
-            D -> E, F;
-          }
-          """
-    tree = parse_string(str)
+    code = """
+           diagram {
+             A -> B -> C [style = dotted];
+             D -> E, F;
+           }
+           """
+    tree = parse_string(code)
     assert isinstance(tree, Graph)
     print tree.stmts
     assert len(tree.stmts) == 2
@@ -96,17 +97,17 @@ def test_parser_diagram_includes_edges():
 
 
 def test_parser_diagram_includes_groups():
-    str = """
-          diagram {
-            group {
-              A; B;
-            }
-            group {
-              C -> D;
-            }
-          }
-          """
-    tree = parse_string(str)
+    code = """
+           diagram {
+             group {
+               A; B;
+             }
+             group {
+               C -> D;
+             }
+           }
+           """
+    tree = parse_string(code)
     assert isinstance(tree, Graph)
     assert len(tree.stmts) == 2
 
@@ -123,18 +124,18 @@ def test_parser_diagram_includes_groups():
 
 
 def test_parser_diagram_includes_diagram_attributes():
-    str = """
-          diagram {
-            fontsize = 12;
-            node_width = 80;
-          }
-          """
-    tree = parse_string(str)
+    code = """
+           diagram {
+             fontsize = 12;
+             node_width = 80;
+           }
+           """
+    tree = parse_string(code)
     assert isinstance(tree, Graph)
     assert len(tree.stmts) == 2
 
 
 @raises(ParseException)
 def test_parser_parenthesis_ness():
-    str = ""
-    tree = parse_string(str)
+    code = ""
+    parse_string(code)
diff --git a/src/blockdiag/tests/test_pep8.py b/src/blockdiag/tests/test_pep8.py
index df254b0..5d946eb 100644
--- a/src/blockdiag/tests/test_pep8.py
+++ b/src/blockdiag/tests/test_pep8.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 
 import os
+import sys
 import pep8
 
 CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -8,31 +9,44 @@ BASE_DIR = os.path.dirname(CURRENT_DIR)
 
 
 def test_pep8():
-    arglist = [
-        '--statistics',
-        '--filename=*.py',
-        '--show-source',
-        '--repeat',
-        '--exclude=SVGdraw.py',
-        #'--show-pep8',
-        #'-qq',
-        #'-v',
-        BASE_DIR,
-    ]
+    arglist = [['statistics', True],
+               ['show-source', True],
+               ['repeat', True],
+               ['paths', [BASE_DIR]]]
 
-    options, args = pep8.process_options(arglist)
-    runner = pep8.input_file
+    pep8style = pep8.StyleGuide(arglist, parse_argv=False, config_file=True)
+    options = pep8style.options
+    if options.doctest:
+        import doctest
+        fail_d, done_d = doctest.testmod(report=False, verbose=options.verbose)
+        fail_s, done_s = pep8.selftest(options)
+        count_failed = fail_s + fail_d
+        if not options.quiet:
+            count_passed = done_d + done_s - count_failed
+            print("%d passed and %d failed." % (count_passed, count_failed))
+            if count_failed:
+                print("Test failed.")
+            else:
+                print("Test passed.")
+        if count_failed:
+            sys.exit(1)
+    if options.testsuite:
+        pep8.init_tests(pep8style)
+    report = pep8style.check_files()
+    if options.statistics:
+        report.print_statistics()
+    if options.benchmark:
+        report.print_benchmark()
+    if options.testsuite and not options.quiet:
+        report.print_results()
+    if report.total_errors:
+        if options.count:
+            sys.stderr.write(str(report.total_errors) + '\n')
+        #sys.exit(1)
 
-    for path in args:
-        if os.path.isdir(path):
-            pep8.input_dir(path, runner=runner)
-        elif not pep8.excluded(path):
-            options.counters['files'] += 1
-            runner(path)
-
-    pep8.print_statistics()
-    errors = pep8.get_count('E')
-    warnings = pep8.get_count('W')
+    # reporting errors (additional summary)
+    errors = report.get_count('E')
+    warnings = report.get_count('W')
     message = 'pep8: %d errors / %d warnings' % (errors, warnings)
     print message
-    assert errors + warnings == 0, message
+    assert report.total_errors == 0, message
diff --git a/src/blockdiag/tests/test_rst_directives.py b/src/blockdiag/tests/test_rst_directives.py
index 2c9f82f..956306e 100644
--- a/src/blockdiag/tests/test_rst_directives.py
+++ b/src/blockdiag/tests/test_rst_directives.py
@@ -1,13 +1,12 @@
 # -*- coding: utf-8 -*-
 
-import re
 import os
-import sys
 import tempfile
 import unittest2
-from utils import stderr_wrapper, assertRaises
+from blockdiag.tests.utils import stderr_wrapper, assertRaises
+
 from docutils import nodes
-from docutils.core import publish_doctree
+from docutils.core import publish_doctree, publish_parts
 from docutils.parsers.rst import directives as docutils
 from blockdiag.utils.rst import directives
 
@@ -28,8 +27,8 @@ def use_tmpdir(func):
             tmpdir = tempfile.mkdtemp()
             func(self, tmpdir)
         finally:
-            for file in os.listdir(tmpdir):
-                os.unlink(tmpdir + "/" + file)
+            for filename in os.listdir(tmpdir):
+                os.unlink(tmpdir + "/" + filename)
             os.rmdir(tmpdir)
 
     _.__name__ = func.__name__
@@ -43,23 +42,35 @@ class TestRstDirectives(unittest2.TestCase):
 
     def test_setup(self):
         directives.setup()
+        options = directives.directive_options
 
         self.assertIn('blockdiag', docutils._directives)
         self.assertEqual(directives.BlockdiagDirective,
                          docutils._directives['blockdiag'])
-        self.assertEqual('PNG', directives.format)
-        self.assertEqual(False, directives.antialias)
-        self.assertEqual(None, directives.fontpath)
+        self.assertEqual('PNG', options['format'])
+        self.assertEqual(False, options['antialias'])
+        self.assertEqual(None, options['fontpath'])
+        self.assertEqual(False, options['nodoctype'])
+        self.assertEqual(False, options['noviewbox'])
+        self.assertEqual(False, options['inline_svg'])
+        self.assertEqual(False, options['ignore_pil'])
 
     def test_setup_with_args(self):
-        directives.setup(format='SVG', antialias=True, fontpath='/dev/null')
+        directives.setup(format='SVG', antialias=True, fontpath='/dev/null',
+                         nodoctype=True, noviewbox=True, inline_svg=True,
+                         ignore_pil=True)
+        options = directives.directive_options
 
         self.assertIn('blockdiag', docutils._directives)
         self.assertEqual(directives.BlockdiagDirective,
                          docutils._directives['blockdiag'])
-        self.assertEqual('SVG', directives.format)
-        self.assertEqual(True, directives.antialias)
-        self.assertEqual('/dev/null', directives.fontpath)
+        self.assertEqual('SVG', options['format'])
+        self.assertEqual(True, options['antialias'])
+        self.assertEqual('/dev/null', options['fontpath'])
+        self.assertEqual(True, options['nodoctype'])
+        self.assertEqual(True, options['noviewbox'])
+        self.assertEqual(True, options['inline_svg'])
+        self.assertEqual(True, options['ignore_pil'])
 
     @stderr_wrapper
     @setup_directive_base
@@ -155,7 +166,7 @@ class TestRstDirectives(unittest2.TestCase):
         directives.setup(format='SVG', fontpath=['dummy.ttf'],
                          outputdir=path)
         text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
-        doctree = publish_doctree(text)
+        publish_doctree(text)
 
     @use_tmpdir
     @assertRaises(RuntimeError)
@@ -163,7 +174,7 @@ class TestRstDirectives(unittest2.TestCase):
         directives.setup(format='SVG', fontpath='dummy.ttf',
                          outputdir=path)
         text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
-        doctree = publish_doctree(text)
+        publish_doctree(text)
 
     @use_tmpdir
     def test_caption(self, path):
@@ -191,6 +202,113 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertFalse(0, doctree[0]['target'].index(path))
 
     @use_tmpdir
+    def test_block_nodoctype_false(self, path):
+        directives.setup(format='SVG', outputdir=path, nodoctype=False)
+        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
+        doctree = publish_doctree(text)
+        self.assertEqual(1, len(doctree))
+        self.assertEqual(nodes.image, type(doctree[0]))
+        svg = open(doctree[0]['uri']).read()
+        self.assertEqual("<?xml version='1.0' encoding='UTF-8'?>\n"
+                         "<!DOCTYPE ", svg[:49])
+
+    @use_tmpdir
+    def test_block_nodoctype_true(self, path):
+        directives.setup(format='SVG', outputdir=path, nodoctype=True)
+        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
+        doctree = publish_doctree(text)
+        self.assertEqual(1, len(doctree))
+        self.assertEqual(nodes.image, type(doctree[-1]))
+        svg = open(doctree[0]['uri']).read()
+        self.assertNotEqual("<?xml version='1.0' encoding='UTF-8'?>\n"
+                            "<!DOCTYPE ", svg[:49])
+
+    @use_tmpdir
+    def test_block_noviewbox_false(self, path):
+        directives.setup(format='SVG', outputdir=path, noviewbox=False)
+        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
+        doctree = publish_doctree(text)
+        self.assertEqual(1, len(doctree))
+        self.assertEqual(nodes.image, type(doctree[0]))
+        svg = open(doctree[0]['uri']).read()
+        self.assertRegexpMatches(svg, '<svg viewBox="0 0 \d+ \d+" ')
+
+    @use_tmpdir
+    def test_block_noviewbox_true(self, path):
+        directives.setup(format='SVG', outputdir=path, noviewbox=True)
+        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
+        doctree = publish_doctree(text)
+        self.assertEqual(1, len(doctree))
+        self.assertEqual(nodes.image, type(doctree[0]))
+        svg = open(doctree[0]['uri']).read()
+        self.assertRegexpMatches(svg, '<svg height="\d+" width="\d+" ')
+
+    @use_tmpdir
+    def test_block_inline_svg_false(self, path):
+        directives.setup(format='SVG', outputdir=path, inline_svg=False)
+        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
+        doctree = publish_doctree(text)
+        self.assertEqual(1, len(doctree))
+        self.assertEqual(nodes.image, type(doctree[0]))
+        self.assertEqual(1, len(os.listdir(path)))
+
+    @use_tmpdir
+    def test_block_inline_svg_true(self, path):
+        directives.setup(format='SVG', outputdir=path, inline_svg=True)
+        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
+        doctree = publish_doctree(text)
+        self.assertEqual(1, len(doctree))
+        self.assertEqual(nodes.raw, type(doctree[0]))
+        self.assertEqual('html', doctree[0]['format'])
+        self.assertEqual(nodes.Text, type(doctree[0][0]))
+        self.assertEqual("<?xml version='1.0' encoding='UTF-8'?>\n"
+                         "<!DOCTYPE ", doctree[0][0][:49])
+        self.assertEqual(0, len(os.listdir(path)))
+
+    @use_tmpdir
+    def test_block_inline_svg_true_but_nonsvg_format(self, path):
+        directives.setup(format='PNG', outputdir=path, inline_svg=True)
+        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
+        doctree = publish_doctree(text)
+        self.assertEqual(1, len(doctree))
+        self.assertEqual(nodes.image, type(doctree[0]))
+
+    @use_tmpdir
+    def test_block_inline_svg_true_with_multibytes(self, path):
+        directives.setup(format='SVG', outputdir=path,
+                         inline_svg=True, ignore_pil=True)
+        text = u".. blockdiag::\n   :alt: hello world\n\n   { あ -> い }"
+        publish_parts(source=text)
+
+    @use_tmpdir
+    def test_block_max_width_inline_svg(self, path):
+        directives.setup(format='SVG', outputdir=path,
+                         nodoctype=True, noviewbox=True, inline_svg=True)
+        text = ".. blockdiag::\n   :maxwidth: 100\n\n   { A -> B }"
+        doctree = publish_doctree(text)
+        self.assertEqual(1, len(doctree))
+        self.assertEqual(nodes.raw, type(doctree[0]))
+        self.assertEqual(nodes.Text, type(doctree[0][0]))
+        self.assertRegexpMatches(doctree[0][0],
+                                 '<svg height="\d+" width="100" ')
+
+    @use_tmpdir
+    def test_block_ignore_pil_false(self, path):
+        directives.setup(format='SVG', outputdir=path, ignore_pil=False)
+        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
+        doctree = publish_doctree(text)
+        self.assertEqual(1, len(doctree))
+        self.assertEqual(nodes.image, type(doctree[0]))
+
+    @use_tmpdir
+    def test_block_ignore_pil_true(self, path):
+        directives.setup(format='SVG', outputdir=path, ignore_pil=True)
+        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
+        doctree = publish_doctree(text)
+        self.assertEqual(1, len(doctree))
+        self.assertEqual(nodes.image, type(doctree[0]))
+
+    @use_tmpdir
     def test_desctable_without_description(self, path):
         directives.setup(format='SVG', outputdir=path)
         text = ".. blockdiag::\n   :desctable:\n\n   { A -> B }"
diff --git a/src/blockdiag/tests/test_utils_fontmap.py b/src/blockdiag/tests/test_utils_fontmap.py
index 9581c2f..6bd4b8a 100644
--- a/src/blockdiag/tests/test_utils_fontmap.py
+++ b/src/blockdiag/tests/test_utils_fontmap.py
@@ -4,7 +4,8 @@ import os
 import sys
 import tempfile
 import unittest2
-from utils import stderr_wrapper, assertRaises
+from blockdiag.tests.utils import stderr_wrapper, assertRaises
+
 from cStringIO import StringIO
 from blockdiag.utils.collections import namedtuple
 from blockdiag.utils.fontmap import FontInfo, FontMap
@@ -49,7 +50,7 @@ class TestUtilsFontmap(unittest2.TestCase):
         FontInfo("cursive-bold-bold", None, 11)
 
     @assertRaises(AttributeError)
-    def test_fontinfo_invalid_familyname4(self):
+    def test_fontinfo_invalid_familyname5(self):
         FontInfo("SERIF", None, 11)
 
     @assertRaises(TypeError)
@@ -212,7 +213,7 @@ class TestUtilsFontmap(unittest2.TestCase):
         self.assertEqual(self.fontpath[1], font1.path)
         self.assertEqual(11, font1.size)
 
-    def test_fontmap_duplicated_fontentry1(self):
+    def test_fontmap_duplicated_fontentry2(self):
         # this testcase is only for python2.6 or later
         if sys.version_info > (2, 6):
             _config = "[fontmap]\nsansserif: %s\nsansserif-normal: %s\n" % \
@@ -288,7 +289,7 @@ class TestUtilsFontmap(unittest2.TestCase):
         self.assertEqual(20, font4.size)
 
     def test_fontmap_using_fontalias(self):
-        _config = ("[fontmap]\nserif-bold: %s\n" + \
+        _config = ("[fontmap]\nserif-bold: %s\n" +
                    "[fontalias]\ntest = serif-bold\n") % self.fontpath[0]
         config = StringIO(_config)
         fmap = FontMap(config)
diff --git a/src/blockdiag/tests/utils.py b/src/blockdiag/tests/utils.py
index c3009ef..f4cd434 100644
--- a/src/blockdiag/tests/utils.py
+++ b/src/blockdiag/tests/utils.py
@@ -1,11 +1,46 @@
 # -*- coding: utf-8 -*-
 import re
+import sys
 from StringIO import StringIO
 from nose.tools import eq_
-from blockdiag.builder import *
+from blockdiag.builder import ScreenNodeBuilder
 from blockdiag.parser import parse_string
 
 
+def supported_pil():
+    try:
+        import _imagingft
+        _imagingft
+
+        return True
+    except:
+        return False
+
+
+def with_pil(fn):
+    if not supported_pil():
+        fn.__test__ = False
+
+    return fn
+
+
+def supported_pdf():
+    try:
+        import reportlab
+        reportlab
+
+        return True
+    except:
+        return False
+
+
+def with_pdf(fn):
+    if not supported_pdf():
+        fn.__test__ = False
+
+    return fn
+
+
 def argv_wrapper(func, argv=[]):
     def wrap(*args, **kwargs):
         try:
@@ -61,8 +96,8 @@ def __build_diagram(filename):
     testdir = os.path.dirname(__file__)
     pathname = "%s/diagrams/%s" % (testdir, filename)
 
-    str = open(pathname).read()
-    tree = parse_string(str)
+    code = open(pathname).read()
+    tree = parse_string(code)
     return ScreenNodeBuilder.build(tree)
 
 
@@ -82,11 +117,11 @@ def __validate_node_attributes(filename, **kwargs):
                         found = True
 
                 if not found:
-                    raise RuntimeError('edge (%s -> %s) is not found' % \
+                    raise RuntimeError('edge (%s -> %s) is not found' %
                                        (id1, id2))
         else:
             print "[node.%s]" % name
-            for node in (n for n in diagram.nodes  if n.drawable):
+            for node in (n for n in diagram.nodes if n.drawable):
                 print node
                 value = getattr(node, name)
                 eq_(values[node.id], value)
diff --git a/src/blockdiag/utils/PDFTextFolder.py b/src/blockdiag/utils/PDFTextFolder.py
deleted file mode 100644
index 1f9b49a..0000000
--- a/src/blockdiag/utils/PDFTextFolder.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-#  Copyright 2011 Takeshi KOMIYA
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-import math
-from TextFolder import TextFolder
-
-
-class PDFTextFolder(TextFolder):
-    def __init__(self, box, string, font, **kwargs):
-        self.canvas = kwargs.get('canvas')
-        self.font = font
-
-        TextFolder.__init__(self, box, string, font, **kwargs)
-
-    def textsize(self, string):
-        width = self.canvas.stringWidth(string, self.font.path, self.font.size)
-        return (int(math.ceil(width)), self.font.size)
diff --git a/src/blockdiag/utils/PILTextFolder.py b/src/blockdiag/utils/PILTextFolder.py
deleted file mode 100644
index 59e5a6e..0000000
--- a/src/blockdiag/utils/PILTextFolder.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-#  Copyright 2011 Takeshi KOMIYA
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-try:
-    from PIL import Image
-    from PIL import ImageDraw
-    from PIL import ImageFont
-except ImportError:
-    import Image
-    import ImageDraw
-    import ImageFont
-from TextFolder import TextFolder
-from fontmap import parse_fontpath, FontMap
-
-
-class PILTextFolder(TextFolder):
-    def __init__(self, box, string, font, **kwargs):
-        if font.path:
-            path, index = parse_fontpath(font.path)
-            if index:
-                self.ttfont = ImageFont.truetype(path, font.size, index=index)
-            else:
-                self.ttfont = ImageFont.truetype(path, font.size)
-        else:
-            self.ttfont = None
-
-        image = Image.new('1', (1, 1))
-        self.draw = ImageDraw.Draw(image)
-        self.fontsize = font.size
-
-        super(PILTextFolder, self).__init__(box, string, font, **kwargs)
-
-    def textsize(self, string):
-        if self.ttfont is None:
-            size = self.draw.textsize(string, font=self.ttfont)
-
-            font_ratio = self.fontsize * 1.0 / FontMap.BASE_FONTSIZE
-            size = (int(size[0] * font_ratio), int(size[1] * font_ratio))
-        else:
-            size = self.draw.textsize(string, font=self.ttfont)
-
-        return size
diff --git a/src/blockdiag/utils/TextFolder.py b/src/blockdiag/utils/TextFolder.py
deleted file mode 100644
index 7fe50cb..0000000
--- a/src/blockdiag/utils/TextFolder.py
+++ /dev/null
@@ -1,296 +0,0 @@
-# -*- coding: utf-8 -*-
-#  Copyright 2011 Takeshi KOMIYA
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-import re
-import math
-import unicodedata
-from blockdiag.utils import Box, XY
-from blockdiag.utils.fontmap import FontInfo
-
-
-def is_zenkaku(char):
-    u"""
-    Detect given character is Japanese ZENKAKU character
-
-    >>> is_zenkaku(u"A")
-    False
-    >>> is_zenkaku(u"あ")
-    True
-    """
-    char_width = unicodedata.east_asian_width(char)
-    return char_width in u"WFA"
-
-
-def zenkaku_len(string):
-    u"""
-    Count Japanese ZENKAKU characters from string
-
-    >>> zenkaku_len(u"abc")
-    0
-    >>> zenkaku_len(u"あいう")
-    3
-    >>> zenkaku_len(u"あいc")
-    2
-    """
-    return len([x for x in string  if is_zenkaku(x)])
-
-
-def hankaku_len(string):
-    u"""
-    Count non Japanese ZENKAKU characters from string
-
-    >>> hankaku_len(u"abc")
-    3
-    >>> hankaku_len(u"あいう")
-    0
-    >>> hankaku_len(u"あいc")
-    1
-    """
-    return len([x for x in string  if not is_zenkaku(x)])
-
-
-def string_width(string):
-    u"""
-    Measure rendering width of string.
-    Count ZENKAKU-character as 2-point and non ZENKAKU-character as 1-point
-
-    >>> string_width(u"abc")
-    3
-    >>> string_width(u"あいう")
-    6
-    >>> string_width(u"あいc")
-    5
-    """
-    widthmap = {'Na': 1, 'N': 1, 'H': 1, 'W': 2, 'F': 2, 'A': 2}
-    return sum(widthmap[unicodedata.east_asian_width(c)] for c in string)
-
-
-class TextFolder(object):
-    def __init__(self, box, string, font, **kwargs):
-        self.box = box
-        self.string = string
-        self.font = font
-        self.scale = 1
-        self.scale = 1
-        self.halign = kwargs.get('halign', 'center')
-        self.valign = kwargs.get('valign', 'center')
-        self.padding = kwargs.get('padding', 8)
-        self.line_spacing = kwargs.get('line_spacing', 2)
-
-        if kwargs.get('adjustBaseline'):
-            self.adjustBaseline = True
-        else:
-            self.adjustBaseline = False
-
-        self._result = self._lines()
-
-    def textsize(self, string):
-        u"""
-        Measure rendering size (width and height) of line.
-        Returned size will not be exactly as rendered text size,
-        Because this method does not use fonts to measure size.
-
-        >>> box = [0, 0, 100, 50]
-        >>> _font = FontInfo('serif', None, 11)
-        >>> TextFolder(box, "", _font).textsize(u"abc")
-        (19, 11)
-        >>> TextFolder(box, "", _font).textsize(u"あいう")
-        (33, 11)
-        >>> TextFolder(box, "", _font).textsize(u"あいc")
-        (29, 11)
-        >>> font = FontInfo('serif', None, 24)
-        >>> TextFolder(box, "", font).textsize(u"abc")
-        (40, 24)
-        >>> font = FontInfo('serif', None, 18)
-        >>> TextFolder(box, "", font).textsize(u"あいう")
-        (54, 18)
-        """
-        width = zenkaku_len(string) * self.font.size + \
-                hankaku_len(string) * self.font.size * 0.55
-        return (int(math.ceil(width)), self.font.size)
-
-    def height(self):
-        u"""
-        Measure rendering height of text.
-
-        If texts is heighter than bounding box,
-        jut out lines will be cut off.
-
-        >>> box = [0, 0, 100, 50]
-        >>> _font = FontInfo('serif', None, 11)
-        >>> TextFolder(box, u"abc", _font).height()
-        11
-        >>> TextFolder(box, u"abc\\ndef", _font).height()
-        24
-        >>> TextFolder(box, u"abc\\n\\ndef", _font).height()
-        37
-        >>> TextFolder(box, u"abc\\ndef\\nghi\\njkl", _font).height()
-        50
-        >>> TextFolder(box, u"abc\\ndef\\nghi\\njkl\\nmno", _font).height()
-        50
-        >>> font = FontInfo('serif', None, 24)
-        >>> TextFolder(box, u"abc", font).height()
-        24
-        >>> TextFolder(box, u"abc\\ndef", _font, line_spacing=8).height()
-        30
-        >>> font = FontInfo('serif', None, 15)
-        >>> TextFolder(box, u"abc\\ndef", font, line_spacing=8).height()
-        38
-        """
-        height = 0
-        for string in self._result:
-            height += self.textsize(string)[1]
-
-        if len(self._result) > 1:
-            height += (len(self._result) - 1) * self.line_spacing
-
-        return height
-
-    @property
-    def lines(self):
-        size = XY(self.box[2] - self.box[0], self.box[3] - self.box[1])
-
-        if self.valign == 'top':
-            height = self.line_spacing
-        elif self.valign == 'bottom':
-            height = size.y - self.height() - self.line_spacing
-        else:
-            height = int(math.ceil((size.y - self.height()) / 2.0))
-        base_xy = XY(self.box[0], self.box[1])
-
-        for string in self._result:
-            textsize = self.textsize(string)
-
-            halign = size.x - textsize[0] * self.scale
-            if self.halign == 'left':
-                x = self.padding
-            elif self.halign == 'right':
-                x = halign - self.padding
-            else:
-                x = int(math.ceil(halign / 2.0))
-
-            if self.adjustBaseline:
-                height += textsize[1]
-            draw_xy = XY(base_xy.x + x, base_xy.y + height)
-
-            yield string, draw_xy
-
-            if self.adjustBaseline:
-                height += self.line_spacing
-            else:
-                height += textsize[1] + self.line_spacing
-
-    @property
-    def outlinebox(self):
-        corners = []
-        for string, xy in self.lines:
-            textsize = self.textsize(string)
-            width = textsize[0] * self.scale
-            height = textsize[1] * self.scale
-
-            if self.adjustBaseline:
-                xy = XY(xy.x, xy.y - textsize[1])
-
-            corners.append(xy)
-            corners.append(XY(xy.x + width, xy.y + height))
-
-        if corners:
-            box = Box(min(p.x for p in corners) - self.padding,
-                      min(p.y for p in corners) - self.line_spacing,
-                      max(p.x for p in corners) + self.padding,
-                      max(p.y for p in corners) + self.line_spacing)
-        else:
-            box = [self.box[0], self.box[1], self.box[0], self.box[1]]
-
-        return box
-
-    def _splitlines(self):
-        u"""
-        Split text to lines as generator.
-        Every line will be stripped.
-        If text includes characters "\n", treat as line separator.
-
-        >>> box = [0, 0, 100, 50]
-        >>> ft = FontInfo('serif', None, 11)
-        >>> [l for l in TextFolder(box, u"abc", ft)._splitlines()]
-        [u'abc']
-        >>> [l for l in TextFolder(box, u"abc\\ndef", ft)._splitlines()]
-        [u'abc', u'def']
-        >>> [l for l in TextFolder(box, u"abc\\\\ndef", ft)._splitlines()]
-        [u'abc', u'def']
-        >>> [l for l in TextFolder(box, u" abc \\n def ", ft)._splitlines()]
-        [u'abc', u'def']
-        >>> [l for l in TextFolder(box, u" \\nabc\\\\ndef", ft)._splitlines()]
-        [u'abc', u'def']
-        >>> [l for l in TextFolder(box, u" \\\\nab \\\\ncd", ft)._splitlines()]
-        [u'', u'ab', u'cd']
-        >>> [l for l in TextFolder(box, u"abc\\\\\\\\ndef", ft)._splitlines()]
-        [u'abc\\\\ndef']
-        >>> [l for l in TextFolder(box, u"abc\xa5\\\\ndef", ft)._splitlines()]
-        [u'abc\\\\ndef']
-        """
-        string = re.sub('^\s*(.*?)\s*$', '\\1', self.string)
-        string = re.sub('(?:\xa5|\\\\){2}', '\x00', string)
-        string = re.sub('(?:\xa5|\\\\)n', '\n', string)
-        for line in string.splitlines():
-            yield re.sub('\x00', '\\\\', line).strip()
-
-    def _lines(self):
-        lines = []
-        size = (self.box[2] - self.box[0], self.box[3] - self.box[1])
-
-        height = 0
-        truncated = 0
-        for line in self._splitlines():
-            while True:
-                string = line.strip()
-                for i in range(0, len(string)):
-                    length = len(string) - i
-                    metrics = self.textsize(string[0:length])
-
-                    if metrics[0] <= size[0]:
-                        break
-                else:
-                    length = 0
-                    metrics = self.textsize(u" ")
-
-                if size[1] < height + metrics[1]:
-                    truncated = 1
-                    break
-
-                lines.append(string[0:length])
-                height += metrics[1] + self.line_spacing
-
-                line = string[length:]
-                if line == "":
-                    break
-
-        # truncate last line.
-        if len(lines) == 0:
-            pass
-        elif truncated:
-            string = lines.pop()
-            for i in range(0, len(string)):
-                if i == 0:
-                    truncated = string + ' ...'
-                else:
-                    truncated = string[0:-i] + ' ...'
-
-                metrics = self.textsize(truncated)
-                if metrics[0] <= size[0]:
-                    lines.append(truncated)
-                    break
-
-        return lines
diff --git a/src/blockdiag/utils/__init__.py b/src/blockdiag/utils/__init__.py
index a50cdfe..2a3eeb6 100644
--- a/src/blockdiag/utils/__init__.py
+++ b/src/blockdiag/utils/__init__.py
@@ -14,7 +14,8 @@
 #  limitations under the License.
 
 import re
-from namedtuple import namedtuple
+import math
+from blockdiag.utils.collections import namedtuple
 
 
 Size = namedtuple('Size', 'width height')
@@ -34,30 +35,45 @@ class XY(tuple):
 
 
 class Box(list):
-    mapper = dict(x1=0, y1=1, x2=2, y2=3)
+    mapper = dict(x1=0, y1=1, x2=2, y2=3, x=0, y=1)
 
     def __init__(self, x1, y1, x2, y2):
-        return super(Box, self).__init__((x1, y1, x2, y2))
+        super(Box, self).__init__((x1, y1, x2, y2))
 
     def __getattr__(self, name):
         return self[self.mapper[name]]
 
     def __repr__(self):
-        class_name = self.__class__.__name__
-        x1 = self.x1
-        y1 = self.y1
-        width = self.width
-        height = self.height
-        addr = id(self)
-
-        format = "<%(class_name)s (%(x1)s, %(y1)s) " + \
-                 "%(width)dx%(height)d at 0x%(addr)08x>"
-        return format % locals()
+        _format = "<%s (%s, %s) %dx%d at 0x%08x>"
+        params = (self.__class__.__name__, self.x1, self.y1,
+                  self.width, self.height, id(self))
+        return _format % params
 
     def shift(self, x=0, y=0):
         return self.__class__(self.x1 + x, self.y1 + y,
                               self.x2 + x, self.y2 + y)
 
+    def get_padding_for(self, size, **kwargs):
+        valign = kwargs.get('valign', 'center')
+        halign = kwargs.get('halign', 'center')
+        padding = kwargs.get('padding', 0)
+
+        if halign == 'left':
+            dx = padding
+        elif halign == 'right':
+            dx = self.size.width - size.width - padding
+        else:
+            dx = int(math.ceil((self.size.width - size.width) / 2.0))
+
+        if valign == 'top':
+            dy = padding
+        elif valign == 'bottom':
+            dy = self.size.height - size.height - padding
+        else:
+            dy = int(math.ceil((self.size.height - size.height) / 2.0))
+
+        return dx, dy
+
     @property
     def size(self):
         return Size(self.width, self.height)
@@ -105,3 +121,26 @@ class Box(list):
     @property
     def center(self):
         return XY(self.x1 + self.width / 2, self.y1 + self.height / 2)
+
+
+def unquote(string):
+    """
+    Remove quotas from string
+
+    >>> unquote('"test"')
+    'test'
+    >>> unquote("'test'")
+    'test'
+    >>> unquote("'half quoted")
+    "'half quoted"
+    >>> unquote('"half quoted')
+    '"half quoted'
+    """
+    if string:
+        m = re.match('\A(?P<quote>"|\')((.|\s)*)(?P=quote)\Z', string, re.M)
+        if m:
+            return re.sub("\\\\" + m.group(1), m.group(1), m.group(2))
+        else:
+            return string
+    else:
+        return string
diff --git a/src/blockdiag/utils/bootstrap.py b/src/blockdiag/utils/bootstrap.py
index 3b35ee1..2ee10a4 100644
--- a/src/blockdiag/utils/bootstrap.py
+++ b/src/blockdiag/utils/bootstrap.py
@@ -17,12 +17,14 @@ import os
 import re
 import sys
 from optparse import OptionParser
+from blockdiag import imagedraw
 from blockdiag.utils.config import ConfigParser
 from blockdiag.utils.fontmap import parse_fontpath, FontMap
 
 
 class Application(object):
     module = None
+    options = None
 
     def run(self):
         try:
@@ -39,7 +41,7 @@ class Application(object):
             sys.stderr.write(msg)
             return -1
         except Exception, e:
-            if self.options.debug:
+            if self.options and self.options.debug:
                 import traceback
                 traceback.print_exc()
             else:
@@ -52,6 +54,23 @@ class Application(object):
     def create_fontmap(self):
         self.fontmap = create_fontmap(self.options)
 
+        fontpath = self.fontmap.find().path
+        _format = self.options.type.lower()
+        ignore_pil = self.options.ignore_pil
+        if _format in ('png', 'svg') and fontpath and ignore_pil is False:
+            try:
+                try:
+                    from PIL import _imagingft
+                except ImportError:
+                    import _imagingft
+            except:
+                msg = "PIL does not support TrueType fonts, " \
+                      "reinstall PIL (and libfreetype2)"
+                if _format != 'png':
+                    msg += " or use --ignore-pil option"
+
+                raise RuntimeError(msg)
+
     def parse_diagram(self):
         import codecs
         if self.options.input == '-':
@@ -70,10 +89,15 @@ class Application(object):
         drawer = DiagramDraw(self.options.type, diagram,
                              self.options.output, fontmap=self.fontmap,
                              code=self.code, antialias=self.options.antialias,
+                             ignore_pil=self.options.ignore_pil,
                              nodoctype=self.options.nodoctype,
                              transparency=self.options.transparency)
         drawer.draw()
-        drawer.save()
+
+        if self.options.size:
+            drawer.save(size=self.options.size)
+        else:
+            drawer.save()
 
         return 0
 
@@ -106,10 +130,15 @@ class Options(object):
                      help='use FONT to draw diagram', metavar='FONT')
         p.add_option('--fontmap',
                      help='use FONTMAP file to draw diagram', metavar='FONT')
+        p.add_option('--ignore-pil', dest='ignore_pil',
+                     default=False, action='store_true',
+                     help='do not use PIL module forcely (SVG only)')
         p.add_option('--no-transparency', dest='transparency',
                      default=True, action='store_false',
-                     help='do not make transparent background of diagram ' +\
+                     help='do not make transparent background of diagram ' +
                           '(PNG only)')
+        p.add_option('--size',
+                     help='Size of diagram (ex. 320x240)')
         p.add_option('-T', dest='type', default='PNG',
                      help='Output diagram as TYPE format')
         p.add_option('--nodoctype', action='store_true',
@@ -133,17 +162,32 @@ class Options(object):
             self.options.output = basename + ext
 
         self.options.type = self.options.type.upper()
-        if not self.options.type in ('SVG', 'PNG', 'PDF'):
+        try:
+            imagedraw.create(self.options.type, None)
+        except:
             msg = "unknown format: %s" % self.options.type
             raise RuntimeError(msg)
 
+        if self.options.size:
+            matched = re.match('^(\d+)x(\d+)$', self.options.size)
+            if matched:
+                self.options.size = [int(n) for n in matched.groups()]
+            else:
+                msg = "--size option must be formatted as WIDTHxHEIGHT."
+                raise RuntimeError(msg)
+
         if self.options.type == 'PDF':
             try:
                 import reportlab.pdfgen.canvas
+                reportlab.pdfgen.canvas
             except ImportError:
                 msg = "could not output PDF format; Install reportlab."
                 raise RuntimeError(msg)
 
+        if self.options.ignore_pil and self.options.type != 'SVG':
+            msg = "--ignore-pil option work in SVG images."
+            raise RuntimeError(msg)
+
         if self.options.nodoctype and self.options.type != 'SVG':
             msg = "--nodoctype option work in SVG images."
             raise RuntimeError(msg)
@@ -183,33 +227,38 @@ class Options(object):
                 if self.options.fontmap is None:
                     self.options.fontmap = config.get(appname, 'fontmap')
 
+            if config.has_option(appname, 'antialias'):
+                antialias = config.get(appname, 'antialias')
+                if antialias.lower() == 'true':
+                    self.options.antialias = True
+
             if self.options.fontmap is None:
                 self.options.fontmap = configpath
 
 
 def detectfont(options):
-    fonts = [
-        # for Windows
-        'c:/windows/fonts/VL-Gothic-Regular.ttf',
-        'c:/windows/fonts/msgothic.ttf',
-        'c:/windows/fonts/msgoth04.ttc',
-        # for Debian (squeeze)
-        '/usr/share/fonts/truetype/ipafont/ipagp.ttf',
-        '/usr/share/fonts/truetype/vlgothic/VL-PGothic-Regular.ttf',
-        # for Debian (wheezy)
-        '/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf',
-        '/usr/share/fonts/truetype/vlgothic/VL-PGothic-Regular.ttf',
-        # for MacOS
-        '/Library/Fonts/Hiragino Sans GB W3.otf',  # for MacOS
-        '/System/Library/Fonts/AppleGothic.ttf',  # for MacOS
-        # for FreeBSD
-        '/usr/local/share/font-ipa/ipagp.otf',  # for FreeBSD
+    import glob
+    fontdirs = [
+        '/usr/share/fonts',
+        '/Library/Fonts',
+        '/System/Library/Fonts',
+        'c:/windows/fonts',
+        '/usr/local/share/font-*',
+    ]
+    fontfiles = [
+        'ipagp.ttf',
+        'ipagp.otf',
+        'VL-PGothic-Regular.ttf',
+        'Hiragino Sans GB W3.otf',
+        'AppleGothic.ttf',
+        'msgothic.ttf',
+        'msgoth04.ttf',
     ]
 
     fontpath = None
     if options.font:
         for path in options.font:
-            _path, index = parse_fontpath(path)
+            _path, _ = parse_fontpath(path)
             if os.path.isfile(_path):
                 fontpath = path
                 break
@@ -218,11 +267,13 @@ def detectfont(options):
             raise RuntimeError(msg)
 
     if fontpath is None:
-        for path in fonts:
-            _path, index = parse_fontpath(path)
-            if os.path.isfile(_path):
-                fontpath = path
-                break
+        globber = (glob.glob(d) for d in fontdirs)
+        for fontdir in sum(globber, []):
+            for root, _, files in os.walk(fontdir):
+                for font in fontfiles:
+                    if font in files:
+                        fontpath = os.path.join(root, font)
+                        break
 
     return fontpath
 
diff --git a/src/blockdiag/utils/config.py b/src/blockdiag/utils/config.py
index 681f977..d404780 100644
--- a/src/blockdiag/utils/config.py
+++ b/src/blockdiag/utils/config.py
@@ -13,7 +13,6 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-import re
 import sys
 import codecs
 from ConfigParser import SafeConfigParser
diff --git a/src/blockdiag/utils/fontmap.py b/src/blockdiag/utils/fontmap.py
index 359051d..48133f7 100644
--- a/src/blockdiag/utils/fontmap.py
+++ b/src/blockdiag/utils/fontmap.py
@@ -17,8 +17,7 @@ import re
 import os
 import sys
 import copy
-import codecs
-from config import ConfigParser
+from blockdiag.utils.config import ConfigParser
 from blockdiag.utils.collections import namedtuple
 
 
@@ -129,7 +128,7 @@ class FontMap(object):
         self.append_font(self.default_fontfamily, path)
 
     def append_font(self, fontfamily, path):
-        _path, index = parse_fontpath(path)
+        _path, _ = parse_fontpath(path)
         if path is None or os.path.isfile(_path):
             font = FontInfo(fontfamily, path, self.fontsize)
             self.fonts[font.familyname] = font
@@ -142,7 +141,7 @@ class FontMap(object):
 
     def find(self, element=None):
         fontfamily = getattr(element, 'fontfamily', None) or \
-                       self.default_fontfamily
+            self.default_fontfamily
         fontfamily = self.aliases.get(fontfamily, fontfamily)
         fontsize = getattr(element, 'fontsize', None) or self.fontsize
 
diff --git a/src/blockdiag/utils/functools.py b/src/blockdiag/utils/functools.py
new file mode 100644
index 0000000..6d078da
--- /dev/null
+++ b/src/blockdiag/utils/functools.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+#  Copyright 2011 Takeshi KOMIYA
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+
+def partial(func, *args, **keywords):
+    def newfunc(*fargs, **fkeywords):
+        newkeywords = keywords.copy()
+        newkeywords.update(fkeywords)
+        return func(*(args + fargs), **newkeywords)
+    newfunc.func = func
+    newfunc.args = args
+    newfunc.keywords = keywords
+    return newfunc
+
+
+def wraps(src):
+    def newfunc(dest):
+        dest.__module__ = src.__module__
+        dest.__name__ = src.__name__
+        dest.__doc__ = src.__doc__
+        d = getattr(src, '__dict__', {})
+        if hasattr(dest, '__dict__'):
+            dest.__dict__.update(d)
+        else:
+            dest.__dict__ = d.copy()
+        return dest
+    return newfunc
diff --git a/src/blockdiag/utils/images.py b/src/blockdiag/utils/images.py
index a82a3ee..9ad96a4 100644
--- a/src/blockdiag/utils/images.py
+++ b/src/blockdiag/utils/images.py
@@ -14,7 +14,8 @@
 #  limitations under the License.
 
 import re
-import urlutil
+from blockdiag.utils import urlutil
+
 try:
     from PIL import Image
 except ImportError:
@@ -31,7 +32,7 @@ except ImportError:
 
             @property
             def size(self):
-                import jpeg
+                from blockdiag.utils import jpeg
                 import png
 
                 try:
diff --git a/src/blockdiag/utils/jpeg.py b/src/blockdiag/utils/jpeg.py
index d2149e1..1c482cf 100644
--- a/src/blockdiag/utils/jpeg.py
+++ b/src/blockdiag/utils/jpeg.py
@@ -30,9 +30,9 @@ class StreamReader(object):
         return (ord(byte1) << 8) + ord(byte2)
 
     def read_bytes(self, n):
-        bytes = self.stream[self.pos:self.pos + n]
+        _bytes = self.stream[self.pos:self.pos + n]
         self.pos += n
-        return bytes
+        return _bytes
 
 
 class JpegHeaderReader(StreamReader):
diff --git a/src/blockdiag/utils/myitertools.py b/src/blockdiag/utils/myitertools.py
index f49e64d..f67ca4d 100644
--- a/src/blockdiag/utils/myitertools.py
+++ b/src/blockdiag/utils/myitertools.py
@@ -19,7 +19,7 @@ from itertools import cycle
 def istep(seq, step=2):
     iterable = iter(seq)
     while True:
-        yield [iterable.next() for i in range(step)]
+        yield [iterable.next() for _ in range(step)]
 
 
 def stepslice(iterable, steps):
@@ -37,10 +37,10 @@ def stepslice(iterable, steps):
             yield o
         else:
             yield iterable.next()
-            for i in xrange(n - 2):
+            for _ in xrange(n - 2):
                 iterable.next()
             yield iterable.next()
 
         # skip (2)
-        for i in xrange(step.next()):
+        for _ in xrange(step.next()):
             iterable.next()
diff --git a/src/blockdiag/utils/namedtuple.py b/src/blockdiag/utils/namedtuple.py
index edd6e34..ec1fe3f 100644
--- a/src/blockdiag/utils/namedtuple.py
+++ b/src/blockdiag/utils/namedtuple.py
@@ -14,4 +14,5 @@
 #  limitations under the License.
 
 
-from collections import namedtuple
+from blockdiag.utils.collections import namedtuple
+namedtuple
diff --git a/src/blockdiag/utils/rst/directives.py b/src/blockdiag/utils/rst/directives.py
index e232365..77d07db 100644
--- a/src/blockdiag/utils/rst/directives.py
+++ b/src/blockdiag/utils/rst/directives.py
@@ -19,16 +19,22 @@ from docutils import nodes
 from docutils.parsers import rst
 from docutils.statemachine import ViewList
 from blockdiag import parser
-from blockdiag.command import detectfont
 from blockdiag.builder import ScreenNodeBuilder
 from blockdiag.drawer import DiagramDraw
+from blockdiag.utils.bootstrap import detectfont
 from blockdiag.utils.collections import namedtuple
+from blockdiag.utils.rst.nodes import blockdiag
 
 
-format = 'PNG'
-antialias = False
-fontpath = None
-outputdir = None
+directive_options_default = dict(format='PNG',
+                                 antialias=False,
+                                 fontpath=None,
+                                 outputdir=None,
+                                 nodoctype=False,
+                                 noviewbox=False,
+                                 inline_svg=False,
+                                 ignore_pil=False)
+directive_options = {}
 
 
 def relfn2path(env, filename):
@@ -55,10 +61,6 @@ def cmp_node_number(a, b):
     return cmp(n1, n2)
 
 
-class blockdiag(nodes.General, nodes.Element):
-    pass
-
-
 class BlockdiagDirectiveBase(rst.Directive):
     """ Directive to insert arbitrary dot markup. """
     name = "blockdiag"
@@ -132,7 +134,10 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
         if not isinstance(node, self.node_class):
             return results
 
-        diagram = self.node2diagram(node)
+        try:
+            diagram = self.node2diagram(node)
+        except Exception, e:
+            raise self.warning(e.message)
 
         if 'desctable' in node['options']:
             del node['options']['desctable']
@@ -148,25 +153,44 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
 
         return results
 
+    @property
+    def global_options(self):
+        return directive_options
+
     def node2diagram(self, node):
         tree = parser.parse_string(node['code'])
         return ScreenNodeBuilder.build(tree)
 
     def node2image(self, node, diagram):
+        options = node['options']
         filename = self.image_filename(node)
         fontpath = self.detectfont()
-        drawer = DiagramDraw(format, diagram, filename,
-                             font=fontpath, antialias=antialias)
+        _format = self.global_options['format'].lower()
+
+        if _format == 'svg' and self.global_options['inline_svg'] is True:
+            filename = None
 
-        if not os.path.isfile(filename):
+        kwargs = dict(self.global_options)
+        del kwargs['format']
+        drawer = DiagramDraw(_format, diagram, filename, **kwargs)
+
+        if filename is None or not os.path.isfile(filename):
             drawer.draw()
-            drawer.save()
+            content = drawer.save()
+
+            if _format == 'svg' and self.global_options['inline_svg'] is True:
+                size = drawer.pagesize()
+                if 'maxwidth' in options and options['maxwidth'] < size[0]:
+                    ratio = float(options['maxwidth']) / size[0]
+                    new_size = (options['maxwidth'], int(size[1] * ratio))
+                    content = drawer.save(new_size)
+
+                return nodes.raw('', content.decode('utf-8'), format='html')
 
         size = drawer.pagesize()
-        options = node['options']
         if 'maxwidth' in options and options['maxwidth'] < size[0]:
             ratio = float(options['maxwidth']) / size[0]
-            thumb_size = (options['maxwidth'], size[1] * ratio)
+            thumb_size = (options['maxwidth'], int(size[1] * ratio))
 
             thumb_filename = self.image_filename(node, prefix='_thumb')
             if not os.path.isfile(thumb_filename):
@@ -185,6 +209,7 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
 
     def detectfont(self):
         Options = namedtuple('Options', 'font')
+        fontpath = self.global_options['fontpath']
         if isinstance(fontpath, (list, tuple)):
             options = Options(fontpath)
         elif isinstance(fontpath, (str, unicode)):
@@ -196,16 +221,21 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
 
     def image_filename(self, node, prefix='', ext='png'):
         try:
-            from hashlib import sha1 as sha
+            from hashlib import sha1
+            sha = sha1
         except ImportError:
             from sha import sha
+            sha = sha
 
         options = dict(node['options'])
-        options.update(font=fontpath, antialias=antialias)
+        options.update(font=self.global_options['fontpath'],
+                       antialias=self.global_options['antialias'])
         hashseed = node['code'].encode('utf-8') + str(options)
         hashed = sha(hashseed).hexdigest()
 
-        filename = "%s%s-%s.%s" % (self.name, prefix, hashed, format.lower())
+        _format = self.global_options['format']
+        outputdir = self.global_options['outputdir']
+        filename = "%s%s-%s.%s" % (self.name, prefix, hashed, _format.lower())
         if outputdir:
             filename = os.path.join(outputdir, filename)
 
@@ -230,7 +260,7 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
         widths = [25] + [50] * (len(klass.desctable) - 1)
         headers = [klass.attrname[n] for n in klass.desctable]
 
-        descriptions = [n.to_desctable() for n in nodes  if n.drawable]
+        descriptions = [n.to_desctable() for n in nodes if n.drawable]
         descriptions.sort(cmp_node_number)
 
         for i in reversed(range(len(headers))):
@@ -252,7 +282,7 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
 
         widths = [25, 50]
         headers = ['Name', 'Description']
-        descriptions = [e.to_desctable() for e in edges  if e.style != 'none']
+        descriptions = [e.to_desctable() for e in edges if e.style != 'none']
 
         if any(desc[1] for desc in descriptions):
             return self._description_table(descriptions, widths, headers)
@@ -283,6 +313,8 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
             row = nodes.row()
             for attr in desc:
                 entry = nodes.entry()
+                if not isinstance(attr, (str, unicode)):
+                    attr = str(attr)
                 self.state.nested_parse(ViewList([attr], source=attr),
                                         0, entry)
                 row += entry
@@ -293,10 +325,9 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
 
 
 def setup(**kwargs):
-    global format, antialias, fontpath, outputdir
-    format = kwargs.get('format', 'PNG')
-    antialias = kwargs.get('antialias', False)
-    fontpath = kwargs.get('fontpath', None)
-    outputdir = kwargs.get('outputdir', None)
+    global directive_options, directive_options_default
+
+    for key, value in directive_options_default.items():
+        directive_options[key] = kwargs.get(key, value)
 
     rst.directives.register_directive("blockdiag", BlockdiagDirective)
diff --git a/src/blockdiag/DiagramDraw.py b/src/blockdiag/utils/rst/nodes.py
similarity index 87%
rename from src/blockdiag/DiagramDraw.py
rename to src/blockdiag/utils/rst/nodes.py
index e5ccf94..14494b9 100644
--- a/src/blockdiag/DiagramDraw.py
+++ b/src/blockdiag/utils/rst/nodes.py
@@ -13,4 +13,8 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-from blockdiag.drawer import DiagramDraw
+from docutils import nodes
+
+
+class blockdiag(nodes.General, nodes.Element):
+    pass
diff --git a/src/blockdiag/utils/uuid.py b/src/blockdiag/utils/uuid.py
index 4815374..26e28b1 100644
--- a/src/blockdiag/utils/uuid.py
+++ b/src/blockdiag/utils/uuid.py
@@ -14,9 +14,11 @@
 #  limitations under the License.
 
 try:
-    from uuid import uuid1 as uuid
+    from uuid import uuid1
+    uuid = uuid1
 except ImportError:
-    from random import random as uuid
+    from random import random
+    uuid = random
 
 
 def generate():
diff --git a/src/blockdiag_sphinxhelper.py b/src/blockdiag_sphinxhelper.py
index 86dbd91..5e9cc99 100644
--- a/src/blockdiag_sphinxhelper.py
+++ b/src/blockdiag_sphinxhelper.py
@@ -13,9 +13,24 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+import blockdiag.parser
+import blockdiag.builder
+import blockdiag.drawer
+core = blockdiag
+
+import blockdiag.utils.bootstrap
+import blockdiag.utils.collections
+import blockdiag.utils.fontmap
+utils = blockdiag.utils
+
+from blockdiag.utils.rst import nodes
+from blockdiag.utils.rst import directives
+
+# FIXME: obsoleted interface (keep for compatibility)
 from blockdiag import command, parser, builder, drawer
-from blockdiag import parser as diagparser
-from blockdiag import drawer as DiagramDraw
 from blockdiag.utils import collections
 from blockdiag.utils.fontmap import FontMap
 from blockdiag.utils.rst.directives import blockdiag, BlockdiagDirective
+
+(command, parser, builder, drawer, collections,
+ FontMap, blockdiag, BlockdiagDirective)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/blockdiag.git



More information about the debian-science-commits mailing list