[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