summaryrefslogtreecommitdiff
path: root/graphics/dot2tex/dot2tex
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /graphics/dot2tex/dot2tex
Initial commit
Diffstat (limited to 'graphics/dot2tex/dot2tex')
-rw-r--r--graphics/dot2tex/dot2tex/__init__.py0
-rw-r--r--graphics/dot2tex/dot2tex/dot2tex4
-rw-r--r--graphics/dot2tex/dot2tex/dot2tex.py2586
3 files changed, 2590 insertions, 0 deletions
diff --git a/graphics/dot2tex/dot2tex/__init__.py b/graphics/dot2tex/dot2tex/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/graphics/dot2tex/dot2tex/__init__.py
diff --git a/graphics/dot2tex/dot2tex/dot2tex b/graphics/dot2tex/dot2tex/dot2tex
new file mode 100644
index 0000000000..0cff5adb6e
--- /dev/null
+++ b/graphics/dot2tex/dot2tex/dot2tex
@@ -0,0 +1,4 @@
+#!/usr/bin/python
+from dot2tex.dot2tex import main
+if __name__ == '__main__':
+ main() \ No newline at end of file
diff --git a/graphics/dot2tex/dot2tex/dot2tex.py b/graphics/dot2tex/dot2tex/dot2tex.py
new file mode 100644
index 0000000000..034b0718ee
--- /dev/null
+++ b/graphics/dot2tex/dot2tex/dot2tex.py
@@ -0,0 +1,2586 @@
+#!/usr/bin/python
+# -*- coding: Latin-1 -*-
+"""Convert graphviz graphs to LaTeX-friendly formats
+
+Various tools for converting graphs generated by the graphviz library
+to formats for use with LaTeX.
+
+Copyright (c) 2006-2007, Kjell Magne Fauske
+
+"""
+
+# Copyright (c) 2006-2007, Kjell Magne Fauske
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+__author__ = 'Kjell Magne Fauske'
+__version__ = '2.7.0'
+__license__ = 'MIT'
+
+
+from itertools import izip
+from optparse import OptionParser
+import optparse
+import sys, tempfile, os, string,re
+import logging
+import tempfile
+from StringIO import StringIO
+
+# intitalize logging module
+log = logging.getLogger("dot2tex")
+console = logging.StreamHandler()
+console.setLevel(logging.WARNING)
+# set a format which is simpler for console use
+formatter = logging.Formatter('%(levelname)-8s %(message)s')
+# tell the handler to use this format
+console.setFormatter(formatter)
+log.addHandler(console)
+
+
+try:
+ import pydot
+except:
+ log.error("Could not load the pydot module.")
+ sys.exit(1)
+
+DEFAULT_TEXTENCODING = 'utf8'
+DEFAULT_OUTPUT_FORMAT = 'pgf'
+
+# label margins in inches
+DEFAULT_LABEL_XMARGIN = 0.11
+DEFAULT_LABEL_YMARGIN = 0.055
+DEFAULT_EDGELABEL_XMARGIN = 0.01
+DEFAULT_EDGELABEL_YMARGIN = 0.01
+DEFAULT_GRAPHLABEL_XMARGIN = 0.01
+DEFAULT_GRAPHLABEL_YMARGIN = 0.01
+
+DEFAULT_NODE_WIDTH = 0.75
+DEFAULT_NODE_HEIGHT = 0.5
+
+
+# Todo: set papersize based on bb
+# Todo: Fontcolor
+
+# Todo: Support linewidth in draw string
+# Todo: Support linestyle in draw string
+# Todo: Need to reconsider edge draw order.
+# See for instance html2.xdot
+
+
+# Inch to bp conversion factor
+in2bp = 72.0
+
+# Examples of draw strings
+# c 5 -black F 14.000000 11 -Times-Roman T 99 159 0 44 8 -a_1 test
+
+
+special_chars = ['$','\\','%','_','#','{',r'}','^','&']
+special_chars_escape = [r'\$', r'$\backslash$',r'\%',r'\_',r'\#',
+ r'\{',r'\}',r'\^{}',r'\&']
+charmap = dict(zip(special_chars,special_chars_escape))
+
+# Pydot has hardcoded Graphviz' graph, node and edge attributes.
+# Dot2tex extends the DOT language with several new attributes. Fortunately
+# it is possible to modify pydot at runtime to accept the new attributes.
+special_graph_attrs = [
+ 'd2tdocpreamble',
+ 'd2tfigpreamble',
+ 'd2tfigpostamble',
+ 'd2tgraphstyle',
+ 'd2talignstr',
+ 'd2tvalignmode',
+ 'd2tnominsize',
+ 'texlbl',
+ 'd2ttikzedgelabels',
+ 'd2toutputformat',
+ 'd2tstyleonly',
+ 'd2tnodeoptions',
+ 'd2tedgeoptions',
+ 'd2toptions',
+]
+
+special_nodeandedge_attrs = [
+ 'texmode',
+ 'texlbl',
+ 'lblstyle',
+ 'topath',
+ 'exstyle',
+]
+
+# Modify pydot to understand dot2tex's extensions to the DOT language
+pydot.Graph.attributes.extend(special_graph_attrs)
+pydot.Edge.attributes.extend(special_nodeandedge_attrs)
+pydot.Node.attributes.extend(special_nodeandedge_attrs)
+
+helpmsg = """\
+Failed to parse the input data. Is it a valid dot file?
+Try to input xdot data directly. Example:
+ dot -Txdot file.dot | dot2tex > file.tex
+
+If this does not work, check that you have an updated version of PyParsing and
+Graphviz. Users have reported problems with old versions. You can also run
+dot2tex in debug mode using the --debug option:
+ dot2tex --debug file.dot
+A file dot2tex.log will be written to the current directory with detailed
+information useful for debugging."""
+
+def mreplace(s, chararray, newchararray):
+ for a, b in zip(chararray, newchararray):
+ s = s.replace(a, b)
+ return s
+
+def escapeTeXChars(string):
+ r"""Escape the special LaTeX-chars %{}_^
+
+ Examples:
+
+ >>> escapeTeXChars('10%')
+ '10\\%'
+ >>> escapeTeXChars('%{}_^\\$')
+ '\\%\\{\\}\\_\\^{}$\\backslash$\\$'
+ """
+ return "".join([charmap.get(c,c) for c in string])
+
+
+def nsplit(seq, n=2):
+ """Split a sequence into pieces of length n
+
+ If the lengt of the sequence isn't a multiple of n, the rest is discareded.
+ Note that nsplit will strings into individual characters.
+
+ Examples:
+ >>> nsplit('aabbcc')
+ [('a', 'a'), ('b', 'b'), ('c', 'c')]
+ >>> nsplit('aabbcc',n=3)
+ [('a', 'a', 'b'), ('b', 'c', 'c')]
+
+ # Note that cc is discarded
+ >>> nsplit('aabbcc',n=4)
+ [('a', 'a', 'b', 'b')]
+ """
+ return [xy for xy in izip(*[iter(seq)]*n)]
+
+def chunks(s, cl):
+ """Split a string or sequence into pieces of length cl and return an iterator
+ """
+ for i in xrange(0, len(s), cl):
+ yield s[i:i+cl]
+
+def replaceTags(template, tags, tagsreplace):
+ """Replace occurences of tags with tagreplace
+
+ Example:
+ >>> replaceTags('a b c d',('b','d'),{'b':'bbb','d':'ddd'})
+ 'a bbb c ddd'
+ """
+ s = template
+ for tag in tags:
+ replacestr = tagsreplace.get(tag, '')
+ if not replacestr:
+ replacestr = ''
+ s = s.replace(tag, replacestr)
+ return s
+
+def getboolattr(item, key, default):
+ if str(getattr(item,key,'')).lower() == 'true':
+ return True
+ else:
+ return False
+
+def createXdot(dotdata,prog='dot'):
+ # The following code is from the pydot module written by Ero Carrera
+ progs = pydot.find_graphviz()
+
+ #prog = 'dot'
+ if progs is None:
+ return None
+ if not progs.has_key(prog):
+ log.warning('Invalid prog=%s',prog)
+ # Program not found ?!?!
+ return None
+
+ tmp_fd, tmp_name = tempfile.mkstemp()
+ os.close(tmp_fd)
+ f = open(tmp_name,'w')
+ f.write(dotdata)
+ f.close()
+ format = 'xdot'
+ cmd = progs[prog]+' -T'+format+' '+tmp_name
+ log.debug('Creating xdot data with: %s',cmd)
+ stdin, stdout, stderr = os.popen3(cmd,'t')
+ stdin.close()
+ stderr.close()
+
+ # I'm not quite sure why this is necessary, but some files
+ # produces data with line endings that confuses pydot/pyparser.
+ data = stdout.readlines()
+ lines = [line for line in data if line.strip()]
+ data = "".join(lines)
+
+ stdout.close()
+ os.unlink(tmp_name)
+
+ return data
+
+def parseDotData(dotdata):
+ """Wrapper for pydot.graph_from_dot_data
+
+ Redirects error messages to the log.
+ """
+ saveout = sys.stdout
+ fsock = StringIO()
+ sys.stdout = fsock
+ graph = pydot.graph_from_dot_data(dotdata)
+ log.debug('Output from pydot:\n'+fsock.getvalue())
+ fsock.close()
+ sys.stdout = sys.__stdout__
+ return graph
+
+def parseDrawString(drawstring):
+ """Parse drawstring and returns a list of draw operations"""
+ # The draw string parser is a bit clumsy and slow
+ def doeE(c,s):
+ # E x0 y0 w h Filled ellipse ((x-x0)/w)^2 + ((y-y0)/h)^2 = 1
+ # e x0 y0 w h Unfilled ellipse ((x-x0)/w)^2 + ((y-y0)/h)^2 = 1
+ tokens = s.split()[0:4]
+ if not tokens: return None
+ points = map(int,tokens)
+ didx = sum(map(len,tokens))+len(points)+1
+ return didx, (c , points[0], points[1], points[2], points[3])
+
+ def doPLB(c, s):
+ # P n x1 y1 ... xn yn Filled polygon using the given n points
+ # p n x1 y1 ... xn yn Unfilled polygon using the given n points
+ # L n x1 y1 ... xn yn Polyline using the given n points
+ # B n x1 y1 ... xn yn B-spline using the given n control points
+ # b n x1 y1 ... xn yn Filled B-spline using the given n control points
+ tokens = s.split()
+ n = int(tokens[0])
+ points = map(int,tokens[1:n*2+1])
+ didx = sum(map(len,tokens[1:n*2+1]))+n*2+2
+ npoints = nsplit(points, 2)
+ return didx, (c, npoints)
+
+ def doCS(c,s):
+ # C n -c1c2...cn Set fill color.
+ # c n -c1c2...cn Set pen color.
+ # Graphviz uses the following color formats:
+ # "#%2x%2x%2x" Red-Green-Blue (RGB)
+ # "#%2x%2x%2x%2x" Red-Green-Blue-Alpha (RGBA)
+ # H[, ]+S[, ]+V Hue-Saturation-Value (HSV) 0.0 <= H,S,V <= 1.0
+ # string color name
+ tokens = s.split()
+ n = int(tokens[0])
+ tmp = len(tokens[0])+3
+ d = s[tmp:tmp+n]
+ didx = len(d)+tmp+1
+ return didx, (c, d)
+
+ def doFont(c,s):
+ # F s n -c1c2...cn
+ # Set font. The font size is s points. The font name consists of
+ # the n characters following '-'.
+ tokens = s.split()
+ size = tokens[0]
+ n = int(tokens[1])
+ tmp = len(size)+len(tokens[1])+4
+ d = s[tmp:tmp+n]
+ didx = len(d)+tmp
+ return didx, (c, size, d)
+
+ def doText(c,s):
+ # T x y j w n -c1c2...cn
+ # Text drawn using the baseline point (x,y). The text consists of the
+ # n characters following '-'. The text should be left-aligned
+ #(centered, right-aligned) on the point if j is -1 (0, 1), respectively.
+ # The value w gives the width of the text as computed by the library.
+ tokens = s.split()
+ x, y, j, w = tokens[0:4]
+ n = int(tokens[4])
+ tmp = sum(map(len,tokens[0:5]))+7
+ text = s[tmp:tmp+n]
+ didx = len(text)+tmp
+ return didx, [c, x, y, j, w, text]
+
+ cmdlist = []
+ stat = {}
+ idx = 0
+ s = drawstring.strip()
+ while idx < len(s)-1:
+ didx = 1
+ c = s[idx]
+ stat[c] = stat.get(c,0)+1
+ try:
+ if c in ('e','E'):
+ didx, cmd = doeE(c,s[idx+1:])
+ cmdlist.append(cmd)
+ elif c in ('p','P','L','b','B'):
+ didx, cmd = doPLB(c, s[idx+1:])
+ cmdlist.append(cmd)
+ elif c in ('c','C','S'):
+ didx, cmd = doCS(c, s[idx+1:])
+ cmdlist.append(cmd)
+ elif c == 'F':
+ didx, cmd = doFont(c, s[idx+1:])
+ cmdlist.append(cmd)
+ elif c == 'T':
+ didx, cmd = doText(c, s[idx+1:])
+ cmdlist.append(cmd)
+
+ except:
+ pass
+
+ idx += didx
+ return cmdlist,stat
+
+
+
+def getGraphList(gg, l = []):
+ """Traverse a graph with subgraphs and return them as a list"""
+ if not l:
+ outer = True
+ else:
+ outer = False
+ l.append(gg)
+ if gg.subgraph_list:
+ for g in gg.subgraph_list:
+ getGraphList(g,l)
+ if outer: return l
+
+class EndOfGraphElement:
+ def __init__(self):
+ pass
+
+def getAllGraphElements(graph, l=[]):
+ """Return all nodes and edges, including elements in subgraphs"""
+ if not l:
+ outer = True
+ l.append(graph)
+ else:
+ outer = False
+ for element in graph.sorted_graph_elements:
+ if isinstance(element, pydot.Node):
+ l.append(element)
+ elif isinstance(element,pydot.Edge):
+ l.append(element)
+ elif isinstance(element, pydot.Graph):
+ l.append(element)
+ getAllGraphElements(element,l)
+ else:
+ log.warning('Unknown graph element')
+
+ if outer:
+ return l
+ else:
+ l.append(EndOfGraphElement())
+
+
+class DotConvBase:
+ """Dot2TeX converter base"""
+ def __init__(self, options = {}):
+ self.color = ""
+ self.template = options.get('template','')
+ self.textencoding = options.get('encoding',DEFAULT_TEXTENCODING)
+ self.templatevars = {}
+ self.body = ""
+ if options.get('templatefile',''):
+ self.loadTemplate(options['templatefile'])
+
+ self.options = options
+ if options.get('texpreproc',False) or options.get('autosize',False):
+ self.dopreproc = True
+ else:
+ self.dopreproc = False
+
+ def loadTemplate(self, templatefile):
+ try:
+ self.template = open(templatefile).read()
+ except:
+ pass
+
+ def convertFile(self, filename):
+ """Load dot file and convert"""
+ pass
+
+ def startFig(self):
+ return ""
+
+ def endFig(self):
+ return ""
+
+ def drawEllipse(self, drawop, style = None):
+ return ""
+
+ def drawBezier(self, drawop, style = None):
+ return ""
+
+ def drawPolygon(self, drawop, style = None):
+ return ""
+
+ def drawPolyLine(self, drawop, style = None):
+ return ""
+
+ def drawText(self, drawop, style = None):
+ return ""
+
+ def outputNodeComment(self, node):
+ return " %% Node: %s\n" % node.name
+
+ def outputEdgeComment(self, edge):
+ src = edge.get_source()
+ dst = edge.get_destination()
+ if self.directedgraph:
+ edge = '->'
+ else:
+ edge = '--'
+ return " %% Edge: %s %s %s\n" % (src, edge, dst)
+
+ def setColor(self, node):
+ return ""
+
+ def setStyle(self, node):
+ return ""
+
+ def drawEdge(self, edge):
+ return ""
+
+ def startNode(self, node):
+ return ""
+ def endNode(self,node):
+ return ""
+ def startGraph(self, graph):
+ return ""
+ def endGraph(self,graph):
+ return ""
+
+ def startEdge(self):
+ return ""
+
+ def endEdge(self):
+ return ""
+
+ def filterStyles(self, style):
+ return style
+
+ def convertColor(self, drawopcolor,pgf=False):
+ """Convert color to a format usable by LaTeX and XColor"""
+ # Graphviz uses the following color formats:
+ # "#%2x%2x%2x" Red-Green-Blue (RGB)
+ # "#%2x%2x%2x%2x" Red-Green-Blue-Alpha (RGBA)
+ # H[, ]+S[, ]+V Hue-Saturation-Value (HSV) 0.0 <= H,S,V <= 1.0
+ # string color name
+
+ # Is the format RBG(A)?
+ if drawopcolor.startswith('#'):
+ t = list(chunks(drawopcolor[1:],2))
+ # parallell lines not yet supported
+ if len(t) > 6:
+ t = t[0:3]
+ rgb = [(round((int(n,16)/255.0),2)) for n in t]
+ if pgf:
+ colstr = "{rgb}{%s,%s,%s}" % tuple(rgb[0:3])
+ opacity = "1"
+ if len(rgb)==4:
+ opacity = rgb[3]
+ return (colstr, opacity)
+ else:
+ return "[rgb]{%s,%s,%s}" % tuple(rgb[0:3])
+
+ elif (len(drawopcolor.split(' '))==3) or (len(drawopcolor.split(','))==3):
+ # are the values space or comma separated?
+ hsb = drawopcolor.split(',')
+ if not len(hsb) == 3:
+ hsb = drawopcolor.split(' ')
+ if pgf:
+ return "{hsb}{%s,%s,%s}" % tuple(hsb)
+ else:
+ return "[hsb]{%s,%s,%s}" % tuple(hsb)
+ else:
+ drawopcolor = drawopcolor.replace('grey','gray')
+ drawopcolor = drawopcolor.replace('_','')
+ drawopcolor = drawopcolor.replace(' ','')
+ return drawopcolor
+
+
+
+ def doDrawString(self, drawstring, drawobj):
+ """Parse and draw drawsting
+
+ Just a wrapper around doDrawOp.
+ """
+ drawoperations,stat = parseDrawString(drawstring)
+ return self.doDrawOp(drawoperations, drawobj,stat)
+
+ def doDrawOp(self, drawoperations, drawobj,stat):
+ """Excecute the operations in drawoperations"""
+ s = ""
+ for drawop in drawoperations:
+ op = drawop[0]
+ style = getattr(drawobj, 'style',None)
+ # styles are not passed to the draw operations in the
+ # duplicate mode
+ if style and not self.options.get('duplicate', False):
+ # map Graphviz styles to backend styles
+ style = self.filterStyles(style)
+ styles = [self.styles.get(key.strip(),key.strip()) \
+ for key in style.split(',') if key]
+ style = ','.join(styles)
+ else:
+ style = None
+
+ if op in ['e','E']:
+ s += self.drawEllipse(drawop, style)
+ elif op in ['p','P']:
+ s += self.drawPolygon(drawop, style)
+ elif op == 'L':
+ s += self.drawPolyLine(drawop, style)
+ elif op in ['C','c']:
+ s += self.setColor(drawop)
+ elif op == 'S':
+ s += self.setStyle(drawop)
+ elif op in ['B']:
+ s += self.drawBezier(drawop, style)
+ elif op in ['T']:
+ # Need to decide what to do with the text
+ # Note that graphviz removes the \ character from the draw
+ # string. Use \\ instead
+ # Todo: Use text from node|edge.label or name
+ # Todo: What about multiline labels?
+ text = drawop[5]
+## label = getattr(drawobj,'label','\N')
+## multiline = False
+## if label:
+## if label.find(r'\n') >= 0:
+## multiline = True
+## #print label
+## else:
+## label = "\N"
+## if not multiline and label <> '\N':
+## text = drawobj.label
+ texmode = self.options.get('texmode','verbatim')
+ if getattr(drawobj,'texmode', ''):
+ texmode = drawobj.texmode
+ if getattr(drawobj,'texlbl', ''):
+ # the texlbl overrides everything
+ text = drawobj.texlbl
+ elif texmode == 'verbatim':
+ # verbatim mode
+ text = escapeTeXChars(text)
+ pass
+ elif texmode == 'math':
+ # math mode
+ text = "$%s$" % text
+
+ drawop[5] = text
+ if self.options.get('alignstr',''):
+ drawop.append(self.options.get('alignstr'))
+ if stat['T'] == 1 and \
+ self.options.get('valignmode','center')=='center':
+ # do this for single line only
+ # Todo: Make this optional
+ pos = getattr(drawobj,'lp',None) or \
+ getattr(drawobj,'pos',None)
+ if pos:
+ coord = pos.split(',')
+ if len(coord)==2:
+ drawop[1] = coord[0]
+ drawop[2] = coord[1]
+ pass
+ lblstyle = getattr(drawobj,'lblstyle',None)
+ exstyle = getattr(drawobj,'exstyle','')
+ if exstyle:
+ if lblstyle:
+ lblstyle += ',' +exstyle
+ else:
+ lblstyle = exstyle
+ s += self.drawText(drawop,lblstyle)
+ return s
+
+ def doNodes(self):
+ s = ""
+ for node in self.nodes:
+ self.currentnode = node
+ dstring = getattr(node,'_draw_',"")
+ lstring = getattr(node,'_ldraw_',"")
+
+
+ drawstring = dstring+" "+lstring
+
+ if not drawstring.strip(): continue
+ # detect node type
+ shape = node.shape
+ if not shape:
+ shape = 'ellipse' # default
+ # extract size information
+ x,y = node.pos.split(',')
+
+ # width and height are in inches. Convert to bp units
+ w = float(node.width)*in2bp
+ h = float(node.height)*in2bp
+
+ s += self.outputNodeComment(node)
+ s += self.startNode(node)
+ #drawoperations = parseDrawString(drawstring)
+ s += self.doDrawString(drawstring, node)
+ s += self.endNode(node)
+ self.body += s
+
+ def getEdgePoints(self, edge):
+ points = edge.pos.split(' ')
+ # check direction
+ arrowstyle = '--'
+ i = 0
+ if points[i].startswith('s'):
+ p = points[0].split(',')
+ tmp = "%s,%s" % (p[1],p[2])
+ if points[1].startswith('e'):
+ points[2] =tmp
+ else:
+ points[1] = tmp
+ del points[0]
+ arrowstyle = '<-'
+ i += 1
+ if points[0].startswith('e'):
+ p = points[0].split(',')
+ points.pop()
+ points.append("%s,%s" % (p[1],p[2]))
+ del points[0]
+ arrowstyle = '->'
+ i += 1
+ if i>1: arrowstyle = '<->'
+ return arrowstyle, points
+
+ def doEdges(self):
+ s = ""
+ s += self.setColor(('cC',"black"))
+ for edge in self.edges:
+ dstring = getattr(edge,'_draw_',"")
+ lstring = getattr(edge,'_ldraw_',"")
+ hstring = getattr(edge,'_hdraw_',"")
+ tstring = getattr(edge,'_tdraw_',"")
+ tlstring = getattr(edge,'_tldraw_',"")
+ hlstring = getattr(edge,'_hldraw_',"")
+
+ # Note that the order of the draw strings should be the same
+ # as in the xdot output.
+ drawstring = dstring + " " + hstring + " " + tstring \
+ + " " + lstring + " " + tlstring + " " + hlstring
+ drawop,stat = parseDrawString(drawstring);
+ if not drawstring.strip():
+ continue
+ s += self.outputEdgeComment(edge)
+ if self.options.get('duplicate', False):
+ s += self.startEdge()
+ s += self.doDrawOp(drawop, edge,stat)
+ s += self.endEdge()
+ else:
+ s += self.drawEdge(edge)
+ s += self.doDrawString(lstring+" "+tlstring+" "+hlstring, edge)
+ self.body += s
+
+ def doGraph(self):
+ dstring = getattr(self.graph,'_draw_',"")
+ lstring = getattr(self.graph,'_ldraw_',"")
+ # print lstring
+ # Avoid filling background of graphs with white
+ if dstring.startswith('c 5 -white C 5 -white') \
+ and not getattr(self.graph,'style'):
+ dstring = ''
+ if getattr(self.graph,'_draw_',None):
+ # bug
+ dstring = "c 5 -black " + dstring #self.graph._draw_
+ pass
+ drawstring = dstring+" "+lstring
+ if drawstring.strip():
+ s = self.startGraph(self.graph)
+ g = self.doDrawString(drawstring, self.graph)
+ e = self.endGraph(self.graph)
+ if g.strip():
+ self.body += s +g + e
+
+ def setOptions(self):
+ # process options
+ # Warning! If graph attribute is true and command line option is false,
+ # the graph attribute will be used. Command line option should have
+ # precedence.
+ self.options['alignstr'] = self.options.get('alignstr','') \
+ or getattr(self.maingraph,'d2talignstr','')
+
+ # Todo: bad!
+ self.options['valignmode'] = getattr(self.maingraph,'d2tvalignmode','')\
+ or self.options.get('valignmode','center')
+
+
+ def convert(self, dotdata):
+ # parse data processed by dot.
+ log.debug('Start conversion')
+ try:
+ try:
+ maingraph = parseDotData(dotdata)
+ except:
+ log.info('Failed first attempt to parse graph')
+ if not self.dopreproc:
+ log.info('Could not parse input dotdata directly. '
+ 'Trying to create xdot data.')
+ try:
+ tmpdata = createXdot(dotdata,self.options.get('prog','dot'))
+ log.debug('xdotdata:\n'+tmpdata)
+ maingraph = parseDotData(tmpdata)
+ except:
+ raise
+
+ if not self.dopreproc and not hasattr(maingraph,'xdotversion'):
+ # Older versions of Graphviz does not include the xdotversion
+ # attribute
+ if not (dotdata.find('_draw_') > 0 or dotdata.find('_ldraw_') > 0):
+ # need to convert to xdot format
+ # Warning. Pydot will not include custom attributes
+ log.debug('Trying to create xdotdata')
+
+ tmpdata = createXdot(dotdata,self.options.get('prog','dot'))
+ log.debug('xdotdata:\n'+tmpdata)
+ if tmpdata == None or not tmpdata.strip():
+ log.error('Failed to create xdotdata. Is Graphviz installed?')
+ sys.exit(1)
+ maingraph = parseDotData(tmpdata)
+ else:
+ # old version
+ pass
+
+ self.maingraph = maingraph
+ self.pencolor = ""
+ self.fillcolor = ""
+ self.linewidth = 1
+ # Detect graph type
+ if maingraph.graph_type == 'digraph':
+ self.directedgraph = True
+ else:
+ self.directedgraph = False
+ except:
+ log.error(helpmsg)
+ sys.exit(1)
+ raise
+ if self.dopreproc:
+ return self.doPreviewPreproc()
+
+ # Romove annoying square
+ # Todo: Remove squares from subgraphs. See pgram.dot
+ dstring = getattr(self.maingraph,'_draw_',"")
+ if dstring:
+ self.maingraph._draw_ = ""
+
+ setDotAttr(self.maingraph)
+ self.setOptions()
+
+ # A graph can consists of nested graph. Extract all graphs
+ graphlist = getGraphList(self.maingraph, [])
+
+ self.body += self.startFig()
+
+ # To get correct drawing order we need to iterate over the graphs
+ # multiple times. First we draw the graph graphics, then nodes and
+ # finally the edges.
+
+ # todo: support the outputorder attribute
+ for graph in graphlist:
+ self.graph = graph
+ self.doGraph()
+
+ if not self.options.get('flattengraph',False):
+ for graph in graphlist:
+ self.graph = graph
+ self.nodes = cleanDotNodes(graph)
+ self.edges = graph.edge_list
+ if not self.options.get('switchdraworder',False):
+ self.doEdges() # tmp
+ self.doNodes()
+ else:
+ self.doNodes()
+ self.doEdges()
+ else:
+ nodelist = []
+ edgelist = []
+ for graph in graphlist:
+ self.graph = graph
+ nodelist.extend(cleanDotNodes(graph))
+ edgelist.extend(graph.edge_list)
+ self.nodes = nodelist
+ self.edges = edgelist
+ if not self.options.get('switchdraworder',False):
+ self.doEdges() # tmp
+ self.doNodes()
+ else:
+ self.doNodes()
+ self.doEdges()
+
+ self.body += self.endFig()
+ return self.output()
+
+ def cleanTemplate(self, template):
+ """Remove preprocsection or outputsection"""
+ if not self.dopreproc and self.options.get('codeonly',False):
+ r = re.compile('<<startcodeonlysection>>(.*?)<<endcodeonlysection>>',
+ re.DOTALL | re.MULTILINE)
+ m = r.search(template)
+ if m:
+ return m.group(1).strip()
+ if not self.dopreproc and self.options.get('figonly',False):
+ r = re.compile('<<startfigonlysection>>(.*?)<<endfigonlysection>>',
+ re.DOTALL | re.MULTILINE)
+ m = r.search(template)
+ if m:
+ return m.group(1)
+
+
+ if self.dopreproc:
+ r = re.compile('<<startoutputsection>>.*?<<endoutputsection>>',
+ re.DOTALL | re.MULTILINE)
+ else:
+ r = re.compile('<<startpreprocsection>>.*?<<endpreprocsection>>',
+ re.DOTALL | re.MULTILINE)
+ # remove codeonly and figonly section
+ r2 = re.compile('<<startfigonlysection>>.*?<<endfigonlysection>>',
+ re.DOTALL | re.MULTILINE)
+ tmp = r2.sub('',template)
+ r2 = re.compile('<<startcodeonlysection>>.*?<<endcodeonlysection>>',
+ re.DOTALL | re.MULTILINE)
+ tmp = r2.sub('',tmp)
+ return r.sub('',tmp)
+
+ def initTemplateVars(self):
+ vars = {}
+ # get bounding box
+ bbstr = self.maingraph.bb
+ if bbstr:
+ bb = bbstr.split(',')
+ vars['<<bbox>>'] = "(%sbp,%sbp)(%sbp,%sbp)\n" % (bb[0],bb[1],bb[2],bb[3])
+ vars['<<bbox.x0>>'] = bb[0]
+ vars['<<bbox.y0>>'] = bb[1]
+ vars['<<bbox.x1>>'] = bb[2]
+ vars['<<bbox.y1>>'] = bb[3]
+ vars['<<figcode>>'] = self.body.strip()
+ vars['<<drawcommands>>'] = self.body.strip()
+ vars['<<textencoding>>'] = self.textencoding
+ docpreamble = self.options.get('docpreamble','') \
+ or getattr(self.maingraph, 'd2tdocpreamble','')
+## if docpreamble:
+## docpreamble = docpreamble.replace('\\n','\n')
+ vars['<<docpreamble>>'] = docpreamble
+ vars['<<figpreamble>>'] = self.options.get('figpreamble','') \
+ or getattr(self.maingraph, 'd2tfigpreamble','%')
+ vars['<<figpostamble>>'] = self.options.get('figpostamble','') \
+ or getattr(self.maingraph, 'd2tfigpostamble','')
+ vars['<<graphstyle>>'] = self.options.get('graphstyle','') \
+ or getattr(self.maingraph, 'd2tgraphstyle','')
+ vars['<<margin>>'] = self.options.get('margin','0pt')
+ vars['<<startpreprocsection>>'] = vars['<<endpreprocsection>>'] = ''
+ vars['<<startoutputsection>>'] = vars['<<endoutputsection>>'] = ''
+ if self.options.get('gvcols',False):
+ vars['<<gvcols>>'] = "\input{gvcols.tex}"
+ else:
+ vars['<<gvcols>>'] = ""
+ self.templatevars = vars
+
+ def output(self):
+ self.initTemplateVars()
+ template = self.cleanTemplate(self.template)
+ code = replaceTags(template ,self.templatevars.keys(),
+ self.templatevars)
+ #code = self.template.replace('<<figcode>>', self.body)
+ return code
+
+ def getLabel(self, drawobj):
+ text = ""
+ texmode = self.options.get('texmode','verbatim')
+ if getattr(drawobj,'texmode', ''):
+ texmode = drawobj.texmode
+ text = getattr(drawobj,'label',None)
+
+ #log.warning('text %s %s',text,drawobj.to_string())
+
+ if text == None or text.strip() == '\N':
+
+ if not isinstance(drawobj, pydot.Edge):
+ text = getattr(drawobj,'name',None) or \
+ getattr(drawobj,'graph_name','')
+ else:
+ text = ''
+ elif text.strip() == '\N':
+ text = ''
+ else:
+ text = text.replace("\\\\","\\")
+
+ if getattr(drawobj,'texlbl', ''):
+ # the texlbl overrides everything
+ text = drawobj.texlbl
+ elif texmode == 'verbatim':
+ # verbatim mode
+ text = escapeTeXChars(text)
+ pass
+ elif texmode == 'math':
+ # math mode
+ text = "$%s$" % text
+
+ return text
+
+ # temp
+ def getLabeld(self, drawobj):
+ text = ""
+ texmode = self.options.get('texmode','verbatim')
+ if drawobj.get('texmode', ''):
+ texmode = drawobj["texmode"]
+ text = drawobj.get('label',None)
+ if text == None or text.strip() == '\N':
+ text = drawobj.get('name',None) or \
+ drawobj.get('graph_name','')
+ else:
+ text = text.replace("\\\\","\\")
+
+ if drawobj.get('texlbl', ''):
+ # the texlbl overrides everything
+ text = drawobj["texlbl"]
+ elif texmode == 'verbatim':
+ # verbatim mode
+ text = escapeTeXChars(text)
+ pass
+ elif texmode == 'math':
+ # math mode
+ text = "$%s$" % text
+ return text
+
+ def getNodePreprocCode(self, node):
+ return node['texlbl']
+
+ def getEdgePreprocCode(self,edge):
+ return edge.texlbl
+
+ def getGraphPreprocCode(self,graph):
+ return graph.texlbl
+
+
+ def getMargins(self, element):
+ """Return element margins"""
+ margins = element.__dict__.get('margin',None)
+ #print margins
+ #print type(margins)
+ if margins:
+ margins = margins.split(',')
+ if len(margins) == 1:
+ xmargin = ymargin = float(margins[0])
+ else:
+ xmargin = float(margins[0])
+ ymargin = float(margins[1])
+ else:
+ # use default values
+ if isinstance(element,pydot.Edge):
+ xmargin = DEFAULT_EDGELABEL_XMARGIN
+ ymargin = DEFAULT_EDGELABEL_YMARGIN
+ else:
+ xmargin = DEFAULT_LABEL_XMARGIN
+ ymargin = DEFAULT_LABEL_YMARGIN
+ return (xmargin, ymargin)
+
+ # Todo: Add support for head and tail labels!
+ # Todo: Support rect nodes if possible.
+ def doPreviewPreproc(self):
+ setDotAttr(self.maingraph)
+ self.initTemplateVars()
+ template = self.cleanTemplate(self.template)
+ template = replaceTags(template ,self.templatevars.keys(),
+ self.templatevars)
+ pp = TeXDimProc(template, self.options)
+ processednodes = {}
+ processededges = {}
+ processedgraphs = {}
+
+ usednodes = {}
+ usededges = {}
+ usedgraphs = {}
+ # iterate over every element in the graph
+ counter = 0
+ for element in getAllGraphElements(self.maingraph,[]):
+ if isinstance(element, pydot.Node):
+ # is it a node statement?
+ node = element
+ if node.name.startswith('"'):
+ node_stmt = True
+ else:
+ # node defined in a edge statement (edge_stmt)
+ node_stmt = False
+ name = node.name.replace('"','')
+ if node_stmt:
+ node.name=name
+ #print node.texmode
+ if name=='node' or name=='edge' or name=='graph': continue
+
+ label = self.getLabel(node)
+ tmpnode = processednodes.get(name,None)
+
+
+ # has the node been defined before?
+ if tmpnode:
+ pass
+## # is it a new label?
+## if node.label or node.texlbl:
+## #if tmpnode['texlbl'] <> label and node_stmt:
+## tmpnode['texlbl'] = label
+ else:
+ tmpnode = dict(texlbl=None, width=None, height=None,
+ margin=None, style=None, fixedsize=None,
+ shape=None, texmode=None,label=None,
+ lblstyle=None)
+ processednodes[name]= tmpnode
+ if node.width:
+ tmpnode['width'] = node.width
+ if node.height:
+ tmpnode['height'] = node.height
+ if node.margin:
+ tmpnode['margin'] = node.margin
+ if node.style:
+ tmpnode['style'] = node.style
+ if node.texmode:
+ tmpnode['texmode'] = node.texmode
+ if node.label or node.label=='':
+ tmpnode['label'] = node.label
+ if node.texlbl:
+ tmpnode['texlbl'] = node.texlbl
+ if node.lblstyle:
+ tmpnode['lblstyle'] = node.lblstyle
+ if node.shape:
+ tmpnode['shape'] = node.shape
+ if node.fixedsize:
+ tmpnode['fixedsize'] = node.fixedsize.lower()
+ #log.debug('nodeinfo %s %s',name, node.fixedsize)
+ if node.shape:
+ tmpnode['shape'] = node.shape
+ elif isinstance(element, pydot.Edge):
+ if not element.label: continue
+ # Ensure that the edge name is unique.
+ name = element.src + element.dst +str(counter)
+ label = self.getLabel(element)
+ element.texlbl = label
+ processededges[name]=element
+ elif isinstance(element, pydot.Graph):
+ if not getattr(element,'label',None) and \
+ not getattr(element,'texlbl',None): continue
+ name = element.graph_name
+ label = self.getLabel(element)
+ element.texlbl = label
+ processedgraphs[name]=element
+
+ else:
+ pass
+ counter += 1
+
+ for name, item in processednodes.items():
+ if item['fixedsize'] == 'true' or item['style'] == 'invis':
+ continue
+ if item['shape'] == 'record':
+ log.warning('Record nodes not supported in preprocessing mode: %s',name)
+ continue
+ item['name'] = name
+ texlbl = self.getLabeld(item)
+ if texlbl:
+ item['texlbl'] = texlbl
+ code = self.getNodePreprocCode(item)
+ pp.addSnippet(name,code)
+ #log.warning(item['texlbl'])
+ usednodes[name] = item
+ for name, item in processededges.items():
+ code = self.getEdgePreprocCode(item)
+ pp.addSnippet(name,code)
+ usededges[name] = item
+ for name, item in processedgraphs.items():
+ code = self.getGraphPreprocCode(item)
+ pp.addSnippet(name,code)
+ usedgraphs[name] = item
+
+
+ ok = pp.process()
+
+ if not ok:
+ errormsg = """\
+Failed to preprocess the graph.
+Is the preview LaTeX package installed? ((Debian package preview-latex-style)
+To see what happened, run dot2tex with the --debug option.
+"""
+ log.error(errormsg)
+ sys.exit(1)
+
+ for name,item in usednodes.items():
+ if not item['texlbl']:continue
+ node = pydot.Node('"'+name+'"',texlbl=item['texlbl'],margin=item['margin'])
+ hp,dp,wt = pp.texdims[name]
+ if self.options.get('rawdim',False):
+ # use dimesions from preview.sty directly
+ node.width = wt
+ node.height = hp+dp
+ node.label=" "
+ node.fixedsize='true'
+ self.maingraph.add_node(node)
+ continue
+ xmargin, ymargin = self.getMargins(node)
+ #print xmargin, ymargin
+ ht = hp+dp
+ minwidth = float(item['width'] or DEFAULT_NODE_WIDTH)
+ minheight = float(item['height'] or DEFAULT_NODE_HEIGHT)
+ if self.options.get('nominsize',False):
+ width = wt+2*xmargin
+ height = ht+2*ymargin
+ else:
+
+ if (wt+2*xmargin) < minwidth:
+ width = minwidth
+ #log.warning("%s %s %s %s %s",minwidth,wt,width,name,tmp)
+ pass
+ else:
+ width = wt+2*xmargin
+ height = ht
+ if ((hp+dp)+2*ymargin) < minheight:
+ height = minheight
+ else:
+ height = ht+ 2*ymargin
+ # Treat shapes with equal widht and height differently
+ # Warning! Rectangles will not alway fit inside a circle
+ # Should use the diagonal.
+ if item['shape'] in ['circle','Msquare','doublecircle','Mcircle']:
+ #log.warning('%s %s', name, item['shape'])
+ if wt< height and width < height:
+ width = height
+ else:
+ height=width
+ pass
+
+ node.width = width
+ node.height = height
+ node.label=" "
+
+ node.fixedsize='true'
+ self.maingraph.add_node(node)
+ for name,item in usededges.items():
+
+ edge = item
+ hp,dp,wt = pp.texdims[name]
+ xmargin, ymargin = self.getMargins(edge)
+ labelcode = '<<<table border="0" cellborder="0" cellpadding="0">'\
+ '<tr><td fixedsize="true" width="%s" height="%s">a</td>'\
+ '</tr></table>>>'
+ edge.label=labelcode % ((wt + 2*xmargin)*72, (hp+dp+2*ymargin)*72)
+ for name,item in usedgraphs.items():
+ graph = item
+ hp,dp,wt = pp.texdims[name]
+ xmargin, ymargin = self.getMargins(graph)
+ labelcode = '<<<table border="0" cellborder="0" cellpadding="0">'\
+ '<tr><td fixedsize="true" width="%s" height="%s">a</td>'\
+ '</tr></table>>>'
+ graph.label=labelcode % ((wt + 2*xmargin)*72, (hp+dp+2*ymargin)*72)
+
+ self.maingraph.d2toutputformat = self.options.get('format',
+ DEFAULT_OUTPUT_FORMAT)
+ graphcode = self.maingraph.to_string()
+ graphcode = graphcode.replace('"<<<','<<')
+ graphcode = graphcode.replace('>>>"','>>')
+ return graphcode
+
+
+
+
+PSTRICKS_TEMPLATE = r"""\documentclass{article}
+% <<bbox>>
+\usepackage[x11names]{xcolor}
+\usepackage[<<textencoding>>]{inputenc}
+\usepackage{graphicx}
+\usepackage{pstricks}
+\usepackage{amsmath}
+<<startpreprocsection>>%
+\usepackage[active,auctex]{preview}
+<<endpreprocsection>>%
+<<gvcols>>%
+<<docpreamble>>%
+
+
+\begin{document}
+\pagestyle{empty}
+<<startpreprocsection>>%
+<<preproccode>>%
+<<endpreprocsection>>%
+<<startoutputsection>>%
+\enlargethispage{100cm}
+
+% Start of code
+\begin{pspicture}[linewidth=1bp<<graphstyle>>]<<bbox>>
+ \pstVerb{2 setlinejoin} % set line join style to 'mitre'
+<<figpreamble>>%
+<<drawcommands>>
+<<figpostamble>>%
+\end{pspicture}
+% End of code
+<<endoutputsection>>%
+\end{document}
+%
+<<startfigonlysection>>
+\begin{pspicture}[linewidth=1bp<<graphstyle>>]<<bbox>>
+ \pstVerb{2 setlinejoin} % set line join style to 'mitre'
+<<figpreamble>>%
+<<drawcommands>>
+<<figpostamble>>%
+\end{pspicture}
+<<endfigonlysection>>
+%
+<<startcodeonlysection>>
+<<figpreamble>>%
+<<drawcommands>>
+<<figpostamble>>%
+<<endcodeonlysection>>
+"""
+
+class Dot2PSTricksConv(DotConvBase):
+ """PSTricks converter backend"""
+ def __init__(self, options = {}):
+ DotConvBase.__init__(self, options)
+ if not self.template:
+ self.template = PSTRICKS_TEMPLATE
+ self.styles = dict(
+ dotted = "linestyle=dotted",
+ dashed = "linestyle=dashed",
+ bold = "linewidth=2pt",
+ solid = "",
+ filled = "",
+ )
+
+ def doGraphtmp(self):
+ self.pencolor = "";
+ self.fillcolor = ""
+ self.color = ""
+ self.body += '{\n'
+ DotConvBase.doGraph(self)
+ self.body += '}\n'
+
+ def startFig(self):
+ # get bounding box
+ bbstr = self.maingraph.bb
+ if bbstr:
+ bb = bbstr.split(',')
+ #fillcolor=black,
+ s = "\\begin{pspicture}[linewidth=1bp](%sbp,%sbp)(%sbp,%sbp)\n" % \
+ (bb[0],bb[1],bb[2],bb[3])
+ # Set line style to mitre
+ s += " \pstVerb{2 setlinejoin} % set line join style to 'mitre'\n"
+ #return s
+ return ""
+
+ def endFig(self):
+ #return '\end{pspicture}\n'
+ return ""
+
+ def drawEllipse(self, drawop, style = None):
+ op, x,y,w,h = drawop
+ s = ""
+ #s = " %% Node: %s\n" % node.name
+ if op == 'E':
+ if style:
+ style = style.replace('filled','')
+ stylestr = 'fillstyle=solid'
+ else:
+ stylestr = ""
+
+ if style:
+ if stylestr:
+ stylestr += ','+ style
+ else:
+ stylestr = style
+
+ s += " \psellipse[%s](%sbp,%sbp)(%sbp,%sbp)\n" % (stylestr,x,y, \
+ # w+self.linewidth,h+self.linewidth)
+ w,h)
+
+
+ return s
+
+ def drawPolygon(self, drawop, style = None):
+ op, points = drawop
+ pp = ['(%sbp,%sbp)' % (p[0],p[1]) for p in points]
+ stylestr = ""
+ if op == 'P':
+ if style:
+ style = style.replace('filled','')
+ stylestr = "fillstyle=solid"
+ if style:
+ if stylestr:
+ stylestr += ','+ style
+ else:
+ stylestr = style
+
+ s = " \pspolygon[%s]%s\n" % (stylestr, "".join(pp))
+ return s
+
+ def drawPolyLine(self, drawop, style = None):
+ op, points = drawop
+ pp = ['(%sbp,%sbp)' % (p[0],p[1]) for p in points]
+ s = " \psline%s\n" % "".join(pp)
+ return s
+
+ def drawBezier(self, drawop, style = None):
+ op, points = drawop
+ pp= []
+ for point in points:
+ pp.append("(%sbp,%sbp)" % (point[0],point[1]))
+
+ #points = ['(%sbp, %sbp)' % (p[0],p[1]) for p in points]
+ arrowstyle = ""
+ return " \psbezier{%s}%s\n" % (arrowstyle, "".join(pp))
+
+
+
+ def drawText(self, drawop, style=None):
+
+ if len(drawop)==7:
+ c, x, y, align, w, text, valign = drawop
+ else:
+ c, x, y, align, w, text = drawop
+ valign=""
+ if align == "-1" :
+ alignstr = 'l' # left aligned
+ elif align == "1":
+ alignstr = 'r' # right aligned
+ else:
+ alignstr = "" # centered (default)
+ if alignstr or valign:
+ alignstr = '['+alignstr+valign+']'
+ s = " \\rput%s(%sbp,%sbp){%s}\n" % (alignstr, x,y,text)
+ return s
+
+ def setColor(self, drawop):
+ c, color = drawop
+ #color = color.replace('grey','gray')
+ #self.pencolor = "";
+ #self.fillcolor = ""
+ #self.color = ""
+ color = self.convertColor(color)
+ s = ""
+ if c == 'c':
+ # set pen color
+ if self.pencolor <> color:
+ self.pencolor = color
+ s = " \psset{linecolor=%s}\n" % color
+ else: return ""
+ elif c == 'C':
+ # set fill color
+ if self.fillcolor <> color:
+ self.fillcolor = color
+ s = " \psset{fillcolor=%s}\n" % color
+ else: return ""
+ return s
+
+ def setStyle(self, drawop):
+ c, style = drawop
+ psstyle = self.styles.get(style,"")
+ if psstyle:
+ return " \psset{%s}\n" % psstyle
+ else:
+ return ""
+
+ def filterStyles(self, style):
+ fstyles = []
+ for item in style.split(','):
+ keyval = item.strip()
+ if keyval.find('setlinewidth') < 0:
+ fstyles.append(keyval)
+ return ', '.join(fstyles)
+
+ def startNode(self, node):
+ self.pencolor = "";
+ self.fillcolor = ""
+ self.color = ""
+ return "{%\n"
+
+ def endNode(self,node):
+ return "}%\n"
+
+ def startEdge(self):
+ self.pencolor = "";
+ self.fillcolor = ""
+ return "{%\n"
+
+ def endEdge(self):
+ return "}%\n"
+
+ def startGraph(self, graph):
+ self.pencolor = "";
+ self.fillcolor = ""
+ self.color = ""
+ return "{\n"
+
+ def endGraph(self,node):
+ return "}\n"
+
+
+ def drawEllipseNode(self, x,y,w,h,node):
+ s = " %% Node: %s\n" % node.name
+ s += " \psellipse(%sbp,%sbp)(%sbp,%sbp)\n" % (x,y, \
+ w/2+self.linewidth,h/2+self.linewidth)
+ # label
+ if node.label:
+ label = node.label
+ else:
+ label = node.name
+ s += " \\rput(%sbp,%sbp){$%s$}\n" % (x,y,label)
+
+ return s
+
+
+
+ def drawEdge(self, edge):
+ s = ""
+ if edge.style == 'invis':
+ return ""
+ arrowstyle, points = self.getEdgePoints(edge)
+ if arrowstyle == '--': arrowstyle=''
+ color = getattr(edge,'color','')
+ if self.color <> color:
+ if color:
+ s += self.setColor(('c',color))
+ else:
+ # reset to default color
+ s += self.setColor(('c','black'))
+ pp= []
+ for point in points:
+ p = point.split(',')
+ pp.append("(%sbp,%sbp)" % (p[0],p[1]))
+
+ edgestyle = edge.style
+ styles = []
+ if arrowstyle:
+ styles.append('arrows=%s' % arrowstyle)
+ if edgestyle:
+ edgestyles = [self.styles.get(key.strip(),key.strip()) for key in edgestyle.split(',') if key]
+ styles.extend(edgestyles)
+ if styles:
+ stylestr = ",".join(styles)
+ else:
+ stylestr = ""
+ if not self.options.get('straightedges',False):
+ s += " \psbezier[%s]%s\n" % (stylestr,"".join(pp))
+ else:
+ s += " \psline[%s]%s%s\n" % (stylestr, pp[0], pp[-1])
+ #s += " \psbezier[%s]{%s}%s\n" % (stylestr, arrowstyle,"".join(pp))
+## if edge.label:
+## x,y = edge.lp.split(',')
+## #s += "\\rput(%s,%s){%s}\n" % (x,y,edge.label)
+ return s
+
+ def initTemplateVars(self):
+ DotConvBase.initTemplateVars(self)
+ # Put a ',' before <<graphstyle>>
+ graphstyle = self.templatevars.get('<<graphstyle>>','')
+ if graphstyle:
+ graphstyle = graphstyle.strip()
+ if not graphstyle.startswith(','):
+ graphstyle = ','+graphstyle
+ self.templatevars['<<graphstyle>>'] = graphstyle
+
+
+PGF_TEMPLATE = r"""\documentclass{article}
+\usepackage[x11names, rgb]{xcolor}
+\usepackage[<<textencoding>>]{inputenc}
+\usepackage{tikz}
+\usetikzlibrary{snakes,arrows,shapes}
+\usepackage{amsmath}
+<<startpreprocsection>>%
+\usepackage[active,auctex]{preview}
+<<endpreprocsection>>%
+<<gvcols>>%
+<<cropcode>>%
+<<docpreamble>>%
+
+\begin{document}
+\pagestyle{empty}
+%
+<<startpreprocsection>>%
+<<preproccode>>
+<<endpreprocsection>>%
+%
+<<startoutputsection>>
+\enlargethispage{100cm}
+% Start of code
+% \begin{tikzpicture}[anchor=mid,>=latex',join=bevel,<<graphstyle>>]
+\begin{tikzpicture}[>=latex',join=bevel,<<graphstyle>>]
+ \pgfsetlinewidth{1bp}
+<<figpreamble>>%
+<<drawcommands>>
+<<figpostamble>>%
+\end{tikzpicture}
+% End of code
+<<endoutputsection>>
+%
+\end{document}
+%
+<<startfigonlysection>>
+\begin{tikzpicture}[>=latex,join=bevel,<<graphstyle>>]
+ \pgfsetlinewidth{1bp}
+<<figpreamble>>%
+<<drawcommands>>
+<<figpostamble>>%
+\end{tikzpicture}
+<<endfigonlysection>>
+<<startcodeonlysection>>
+<<figpreamble>>%
+<<drawcommands>>
+<<figpostamble>>%
+<<endcodeonlysection>>
+"""
+
+class Dot2PGFConv(DotConvBase):
+ """PGF/TikZ converter backend"""
+ def __init__(self, options={}):
+ DotConvBase.__init__(self, options)
+ if not self.template:
+ self.template = PGF_TEMPLATE
+ self.styles = dict(dashed='dashed', dotted='dotted',
+ bold='very thick', filled='fill', invis="",
+ rounded='rounded corners', )
+ self.dashstyles = dict(
+ dashed = '\pgfsetdash{{3pt}{3pt}}{0pt}',
+ dotted='\pgfsetdash{{\pgflinewidth}{2pt}}{0pt}',
+ bold = '\pgfsetlinewidth{1.2pt}')
+
+ def startFig(self):
+ # get bounding box
+ # get bounding box
+ s = ""
+## bbstr = self.maingraph.bb
+## if bbstr:
+## bb = bbstr.split(',')
+## s += "%%(%sbp,%sbp)(%sbp,%sbp)\n" % \
+## (bb[0],bb[1],bb[2],bb[3])
+
+ return s
+
+ def endFig(self):
+ #return '\end{tikzpicture}'
+ return ""
+
+ def startNode(self, node):
+ # Todo: Should find a more elgant solution
+ self.pencolor = "";
+ self.fillcolor = ""
+ self.color = ""
+ return "\\begin{scope}\n"
+
+ def endNode(self,node):
+ return "\\end{scope}\n"
+
+ def startEdge(self):
+ # Todo: Should find a more elgant solution
+ #self.pencolor = "";
+ #self.fillcolor = ""
+ #self.color = ""
+ return "\\begin{scope}\n"
+
+ def endEdge(self):
+ return "\\end{scope}\n"
+
+ def startGraph(self, graph):
+ # Todo: Should find a more elgant solution
+ self.pencolor = "";
+ self.fillcolor = ""
+ self.color = ""
+ return "\\begin{scope}\n"
+
+ def endGraph(self,graph):
+ return "\\end{scope}\n"
+ #return "\\end{scope}"
+
+ def setColor(self, drawop):
+ c, color = drawop
+ # Todo: Should find a more elgant solution
+ #self.pencolor = "";
+ #self.fillcolor = ""
+ #self.color = ""
+ res = self.convertColor(color, True)
+ opacity = None
+ if len(res)==2:
+ ccolor, opacity = res
+ else:
+ ccolor = res
+ s = ""
+ if c == 'cC':
+ #self.pencolor = color
+ #self.fillcolor = color
+ if self.color <> color:
+ self.color = color
+ self.pencolor=color
+ self.fillcolor=color
+ if ccolor.startswith('{'):
+ # rgb or hsb
+ s += " \definecolor{newcol}%s;\n" % ccolor
+ ccolor = 'newcol'
+ s += " \pgfsetcolor{%s}\n" % ccolor
+ #s += " \pgfsetfillcolor{%s}\n" % ccolor
+ elif c == 'c':
+ # set pen color
+ if self.pencolor <> color:
+ self.pencolor = color
+ self.color = ''
+ if ccolor.startswith('{'):
+ # rgb or hsb
+ s += " \definecolor{strokecol}%s;\n" % ccolor
+ ccolor = 'strokecol'
+ s += " \pgfsetstrokecolor{%s}\n" % ccolor
+ else: return ""
+ elif c == 'C':
+ # set fill color
+ if self.fillcolor <> color:
+ self.fillcolor = color
+ self.color = ''
+ if ccolor.startswith('{'):
+ # rgb
+ s += " \definecolor{fillcol}%s;\n" % ccolor
+ ccolor = 'fillcol'
+ s += " \pgfsetfillcolor{%s}\n" % ccolor
+ if not opacity == None:
+ self.opacity = opacity
+ # Todo: The opacity should probably be set directly when drawing
+ # The \pgfsetfillcopacity cmd affects text as well
+ #s += " \pgfsetfillopacity{%s};\n" % opacity
+ else:
+ self.opacity = None
+ else: return ""
+ return s
+
+ def setStyle(self, drawop):
+ c, style = drawop
+ pgfstyle = self.dashstyles.get(style,"")
+ if pgfstyle:
+ return " %s\n" % pgfstyle
+ else:
+ return ""
+
+ def filterStyles(self, style):
+ fstyles = []
+ for item in style.split(','):
+ keyval = item.strip()
+ if keyval.find('setlinewidth') < 0 and not keyval=='filled':
+ fstyles.append(keyval)
+ return ', '.join(fstyles)
+
+
+ def drawEllipse(self, drawop, style = None):
+ op, x,y,w,h = drawop
+ s = ""
+ #s = " %% Node: %s\n" % node.name
+ if op == 'E':
+ if self.opacity <> None:
+ # Todo: Need to know the state of the current node
+ cmd = 'filldraw [opacity=%s]' % self.opacity
+ else:
+ cmd = 'filldraw'
+ else:
+ cmd = "draw"
+
+ if style:
+ stylestr = " [%s]" % style
+ else:
+ stylestr = ''
+ s += " \%s%s (%sbp,%sbp) ellipse (%sbp and %sbp);\n" % (cmd, stylestr, x,y, \
+ # w+self.linewidth,h+self.linewidth)
+ w,h)
+ return s
+
+ def drawPolygon(self, drawop, style = None):
+ op, points = drawop
+ pp = ['(%sbp,%sbp)' % (p[0],p[1]) for p in points]
+ cmd = "draw"
+ if op == 'P':
+ cmd = "filldraw"
+
+ if style:
+ stylestr = " [%s]" % style
+ else:
+ stylestr = ''
+ s = " \%s%s %s -- cycle;\n" % (cmd, stylestr, " -- ".join(pp))
+ return s
+
+ def drawPolyLine(self, drawop, style = None):
+ op, points = drawop
+ pp = ['(%sbp,%sbp)' % (p[0],p[1]) for p in points]
+ ##if style:
+## stylestr = " [%s]" % style
+## else:
+## stylestr = ''
+ stylestr = ''
+ return " \draw%s %s;\n" %(stylestr, " -- ".join(pp))
+
+
+ def drawText(self, drawop, style = None):
+ # The coordinates given by drawop are not the same as the node
+ # coordinates! This may give som odd results if graphviz' and
+ # LaTeX' fonts are very different.
+ if len(drawop)==7:
+ c, x, y, align, w, text, valign = drawop
+ else:
+ c, x, y, align, w, text = drawop
+ valign=""
+ styles = []
+ if align == "-1" :
+ alignstr = 'right' # left aligned
+ elif align == "1":
+ alignstr = 'left' # right aligned
+ else:
+ alignstr = "" # centered (default)
+ styles.append(alignstr)
+ styles.append(style)
+## if alignstr:
+## alignstr = "[" + alignstr+", anchor=mid" + "]"
+## else:
+## alignstr = "[anchor=mid]"
+ lblstyle = ",".join([i for i in styles if i])
+ if lblstyle:
+ lblstyle = '['+lblstyle+']'
+ s = " \draw (%sbp,%sbp) node%s {%s};\n" % (x,y,lblstyle,text)
+ return s
+
+ def drawBezier(self, drawop, style = None):
+ s = ""
+ c, points = drawop
+ arrowstyle = '--'
+
+ pp= []
+ for point in points:
+ pp.append("(%sbp,%sbp)" % (point[0],point[1]))
+
+ #points = ['(%sbp, %sbp)' % (p[0],p[1]) for p in points]
+ #quadp = nsplit(pp, 4)
+ pstrs = ["%s .. controls %s and %s " % p for p in nsplit(pp, 3)]
+ ##if style:
+## stylestr = " [%s]" % style
+## else:
+## stylestr = ''
+ stylestr = ''
+## if arrowstyle == '--':
+## style = ''
+## else:
+## style = '[%s]' % arrowstyle
+
+ s += " \draw%s %s .. %s;\n" % (stylestr, " .. ".join(pstrs), pp[-1])
+
+ return s
+
+
+ def doEdges(self):
+ s = ""
+ s += self.setColor(('cC',"black"))
+ for edge in self.edges:
+ dstring = getattr(edge,'_draw_',"")
+ lstring = getattr(edge,'_ldraw_',"")
+ hstring = getattr(edge,'_hdraw_',"")
+ tstring = getattr(edge,'_tdraw_',"")
+ tlstring = getattr(edge,'_tldraw_',"")
+ hlstring = getattr(edge,'_hldraw_',"")
+
+ # Note that the order of the draw strings should be the same
+ # as in the xdot output.
+ drawstring = dstring + " " + hstring + " " + tstring \
+ + " " + lstring + " " + tlstring + " " + hlstring
+ drawop,stat = parseDrawString(drawstring);
+ if not drawstring.strip():
+ continue
+ s += self.outputEdgeComment(edge)
+ if self.options.get('duplicate', False):
+ s += self.startEdge()
+ s += self.doDrawOp(drawop, edge,stat)
+ s += self.endEdge()
+ else:
+ topath = getattr(edge,'topath',None)
+ s += self.drawEdge(edge)
+ if not self.options.get('tikzedgelabels',False) and not topath:
+ s += self.doDrawString(lstring+" "+tlstring+" "+hlstring, edge)
+ else:
+ s += self.doDrawString(tlstring+" "+hlstring, edge)
+ #s += self.drawEdge(edge)
+ #s += self.doDrawString(lstring+" "+tlstring+" "+hlstring, edge)
+ self.body += s
+
+
+ def drawEdge(self, edge):
+ s = ""
+ if edge.style == 'invis':
+ return ""
+ arrowstyle, points = self.getEdgePoints(edge)
+ # PGF uses the fill style when drawing some arrowheads. We have to
+ # ensure that the fill color is the same as the pen color.
+ color = getattr(edge,'color','')
+
+ if self.color <> color:
+ if color:
+ s += self.setColor(('cC',color))
+ else:
+ # reset to default color
+ s += self.setColor(('cC','black'))
+
+ pp= []
+ for point in points:
+ p = point.split(',')
+ pp.append("(%sbp,%sbp)" % (p[0],p[1]))
+
+ edgestyle = edge.style
+
+ styles = []
+ if arrowstyle <> '--':
+ #styles.append(arrowstyle)
+ styles = [arrowstyle]
+
+ if edgestyle:
+ edgestyles = [self.styles.get(key.strip(),key.strip()) for key in edgestyle.split(',') if key]
+ styles.extend(edgestyles)
+
+ stylestr = ",".join(styles)
+ topath = getattr(edge,'topath',None)
+
+ pstrs = ["%s .. controls %s and %s " % p for p in nsplit(pp, 3)]
+ extra = ""
+ if self.options.get('tikzedgelabels',False) or topath:
+ edgelabel = self.getLabel(edge)
+ #log.warning('label: %s', edgelabel)
+ lblstyle = getattr(edge,'lblstyle','')
+ if lblstyle:
+ lblstyle = '['+lblstyle+']'
+ else:
+ lblstyle = ''
+ if edgelabel:
+ extra = " node%s {%s}" % (lblstyle,edgelabel)
+ src = pp[0]
+ dst = pp[-1]
+ if topath:
+ s += " \draw [%s] %s to[%s]%s %s;\n" % (stylestr, src,
+ topath, extra,dst)
+ elif not self.options.get('straightedges',False):
+ #s += " \draw [%s] %s .. %s;\n" % (stylestr, " .. ".join(pstrs), pp[-1])
+ s += " \draw [%s] %s ..%s %s;\n" % (stylestr, " .. ".join(pstrs), extra,pp[-1])
+ else:
+ s += " \draw [%s] %s --%s %s;\n" % (stylestr, pp[0],extra, pp[-1])
+
+ return s
+
+ def initTemplateVars(self):
+ DotConvBase.initTemplateVars(self)
+ if self.options.get('crop',False):
+ cropcode = "\usepackage[active,tightpage]{preview}\n" + \
+ "\PreviewEnvironment{tikzpicture}\n" + \
+ "\setlength\PreviewBorder{%s}" % self.options.get('margin','0pt')
+ else:
+ cropcode = ""
+ vars = {}
+ vars['<<cropcode>>'] = cropcode
+ self.templatevars.update(vars)
+
+ def getNodePreprocCode(self,node):
+ lblstyle = node.get('lblstyle','')
+ text = node.get('texlbl','')
+ if lblstyle:
+ return " \\tikz \\node[%s] {%s};\n" % (lblstyle, text)
+ else:
+ return r"\tikz \node {" + text +"};"
+
+ def getEdgePreprocCode(self,edge):
+ lblstyle = getattr(edge,'lblstyle','')
+ if lblstyle:
+ return " \\tikz \\node[%s] {%s};\n" % (lblstyle, edge.texlbl)
+ else:
+ return r"\tikz \node " + "{"+edge.texlbl +"};"
+
+ def getGraphPreprocCode(self,graph):
+ lblstyle = getattr(graph,'lblstyle','')
+ if lblstyle:
+ return " \\tikz \\node[%s] {%s};\n" % (lblstyle, graph.texlbl)
+ else:
+ return r"\tikz \node " + graph.texlbl +"};"
+
+
+
+
+
+
+TIKZ_TEMPLATE = r"""\documentclass{article}
+\usepackage[x11names, rgb]{xcolor}
+\usepackage[<<textencoding>>]{inputenc}
+\usepackage{tikz}
+\usetikzlibrary{snakes,arrows,shapes}
+\usepackage{amsmath}
+<<startpreprocsection>>%
+\usepackage[active,auctex]{preview}
+<<endpreprocsection>>%
+<<gvcols>>%
+<<cropcode>>%
+<<docpreamble>>%
+
+\begin{document}
+\pagestyle{empty}
+%
+<<startpreprocsection>>%
+<<preproccode>>
+<<endpreprocsection>>%
+%
+<<startoutputsection>>
+\enlargethispage{100cm}
+% Start of code
+\begin{tikzpicture}[>=latex',join=bevel,<<graphstyle>>]
+<<figpreamble>>%
+<<drawcommands>>
+<<figpostamble>>%
+\end{tikzpicture}
+% End of code
+<<endoutputsection>>
+%
+\end{document}
+%
+<<startfigonlysection>>
+\begin{tikzpicture}[>=latex,join=bevel,<<graphstyle>>]
+<<figpreamble>>%
+<<drawcommands>>
+<<figpostamble>>%
+\end{tikzpicture}
+<<endfigonlysection>>
+<<startcodeonlysection>>
+<<figpreamble>>%
+<<drawcommands>>
+<<figpostamble>>%
+<<endcodeonlysection>>
+"""
+
+class Dot2TikZConv(Dot2PGFConv):
+ """A backend that utilizes the node and edge mechanism of PGF/TikZ"""
+ shapemap = {'doublecircle' : 'circle, double',
+ 'box' : 'rectangle',
+ 'rect' : 'rectangle',
+ 'none' :'draw=none',
+ 'plaintext' : 'draw=none',
+ 'polygon' : 'regular polygon, regular polygon sides=7',
+ 'triangle' : 'regular polygon, regular polygon sides=3',
+ 'pentagon' : 'regular polygon, regular polygon sides=5',
+ 'hexagon' : 'regular polygon, regular polygon sides=5',
+ 'septagon' : 'regular polygon, regular polygon sides=7',
+ 'octagon' : 'regular polygon, regular polygon sides=8',
+ }
+
+ def __init__(self, options={}):
+ # to connect nodes they have to defined. Therefore we have to ensure
+ # that code for generating nodes is outputted first.
+ options['switchdraworder'] = True
+ options['flattengraph'] = True
+ options['rawdim'] = True
+ DotConvBase.__init__(self, options)
+ if not self.template:
+ self.template = TIKZ_TEMPLATE
+
+ self.styles = dict(dashed='dashed', dotted='dotted',
+ bold='very thick', filled='fill', invis="",
+ rounded='rounded corners', )
+ self.dashstyles = dict(
+ dashed = '\pgfsetdash{{3pt}{3pt}}{0pt}',
+ dotted='\pgfsetdash{{\pgflinewidth}{2pt}}{0pt}',
+ bold = '\pgfsetlinewidth{1.2pt}')
+
+ def setOptions(self):
+ Dot2PGFConv.setOptions(self)
+ self.options['tikzedgelabels'] = self.options.get('tikzedgelabels','') \
+ or getboolattr(self.maingraph,'d2ttikzedgelabels','')
+ self.options['styleonly'] = self.options.get('styleonly','') \
+ or getboolattr(self.maingraph,'d2tstyleonly','')
+ self.options['nodeoptions'] = self.options.get('nodeoptions','') \
+ or getattr(self.maingraph,'d2tnodeoptions','')
+ self.options['edgeoptions'] = self.options.get('edgeoptions','') \
+ or getattr(self.maingraph,'d2tedgeoptions','')
+
+ def outputNodeComment(self, node):
+ # With the node syntax comments are unnecessary
+ return ""
+
+ def getNodePreprocCode(self,node):
+ lblstyle = node.get('lblstyle','')
+
+ shape = node.get('shape','ellipse')
+ shape = self.shapemap.get(shape, shape)
+ #s += "%% %s\n" % (shape)
+ label = node.get('texlbl','')
+ style = node.get('style'," ") or " ";
+ if lblstyle:
+ if style.strip():
+ style += ','+lblstyle
+ else:
+ style = lblstyle
+
+ sn = ""
+ if self.options.get('styleonly'):
+ sn += "\\tikz \\node (%s) [%s] {%s};\n" % \
+ (style, label)
+ else:
+ sn += "\\tikz \\node [draw,%s,%s] {%s};\n" % \
+ (shape, style, label)
+ return sn
+
+ def doNodes(self):
+ s = ""
+ nodeoptions= self.options.get('nodeoptions',None)
+ if nodeoptions:
+ s += "\\begin{scope}[%s]\n" % nodeoptions
+ for node in self.nodes:
+ self.currentnode = node
+ # detect node type
+ shape = getattr(node,'shape','ellipse')
+ shape = self.shapemap.get(shape, shape)
+ if shape == None:
+ shape='ellipse'
+
+ pos = getattr(node,'pos',None)
+ if not pos:
+ continue
+ x,y = pos.split(',')
+ label = self.getLabel(node)
+
+ pos = "%sbp,%sbp" % (x,y)
+ style = node.style or "";
+ if node.lblstyle:
+ if style:
+ style += ','+node.lblstyle
+ else:
+ style = node.lblstyle
+ if node.exstyle:
+ if style:
+ style += ','+node.exstyle
+ else:
+ style = node.exstyle
+ sn = ""
+ sn += self.outputNodeComment(node)
+ sn += self.startNode(node)
+ if self.options.get('styleonly'):
+ sn += " \\node (%s) at (%s) [%s] {%s};\n" % \
+ (node.name, pos, style, label)
+ else:
+ color = getattr(node,'color','')
+ drawstr = 'draw'
+ if style.strip() == 'filled':
+ drawstr='fill,draw'
+ style = ''
+ if color:
+ drawstr += ','+color
+ elif color:
+ drawstr += '='+color
+
+ if style.strip():
+ sn += " \\node (%s) at (%s) [%s,%s,%s] {%s};\n" % \
+ (node.name, pos, drawstr, shape, style, label)
+ else:
+ sn += " \\node (%s) at (%s) [%s,%s] {%s};\n" % \
+ (node.name, pos, drawstr, shape, label)
+ sn += self.endNode(node)
+
+
+ s += sn
+ if nodeoptions:
+ s += "\\end{scope}\n"
+ self.body += s
+
+ def doEdges(self):
+ s = ""
+ edgeoptions = self.options.get('edgeoptions',None)
+ if edgeoptions:
+ s += "\\begin{scope}[%s]\n" % edgeoptions
+ for edge in self.edges:
+ dstring = getattr(edge,'_draw_',"")
+ lstring = getattr(edge,'_ldraw_',"")
+ hstring = getattr(edge,'_hdraw_',"")
+ tstring = getattr(edge,'_tdraw_',"")
+ tlstring = getattr(edge,'_tldraw_',"")
+ hlstring = getattr(edge,'_hldraw_',"")
+ topath = getattr(edge,'topath',None)
+ s += self.drawEdge(edge)
+ if not self.options.get('tikzedgelabels',False) and not topath:
+ s += self.doDrawString(lstring+" "+tlstring+" "+hlstring, edge)
+ else:
+ s += self.doDrawString(tlstring+" "+hlstring, edge)
+
+ if edgeoptions:
+ s += "\\end{scope}\n"
+ self.body += s
+
+ def drawEdge(self, edge):
+ s = ""
+ if edge.style == 'invis':
+ return ""
+ arrowstyle, points = self.getEdgePoints(edge)
+ # PGF uses the fill style when drawing some arrowheads. We have to
+ # ensure that the fill color is the same as the pen color.
+ color = getattr(edge,'color','')
+ pp= []
+ for point in points:
+ p = point.split(',')
+ pp.append("(%sbp,%sbp)" % (p[0],p[1]))
+
+ edgestyle = edge.style
+ #print edgestyle
+
+ styles = []
+ if arrowstyle <> '--':
+ #styles.append(arrowstyle)
+ styles = [arrowstyle]
+
+ if edgestyle:
+ edgestyles = [self.styles.get(key.strip(),key.strip()) for key in edgestyle.split(',') if key]
+ styles.extend(edgestyles)
+
+ stylestr = ",".join(styles)
+ if color:
+ stylestr = color+','+stylestr
+ src = edge.get_source()
+ dst = edge.get_destination()
+ topath = getattr(edge,'topath',None)
+
+ pstrs = ["%s .. controls %s and %s " % p for p in nsplit(pp, 3)]
+ pstrs[0] = "(%s) ..controls %s and %s " % (src, pp[1], pp[2])
+ extra = ""
+ if self.options.get('tikzedgelabels',False) or topath:
+ edgelabel = self.getLabel(edge)
+ #log.warning('label: %s', edgelabel)
+ lblstyle = getattr(edge,'lblstyle','')
+ exstyle = getattr(edge,'exstyle','')
+ if exstyle:
+ if lblstyle:
+ lblstyle += ',' +exstyle
+ else:
+ lblstyle = exstyle
+ if lblstyle:
+ lblstyle = '['+lblstyle+']'
+ else:
+ lblstyle = ''
+ if edgelabel:
+ extra = " node%s {%s}" % (lblstyle,edgelabel)
+
+ if topath:
+ s += " \draw [%s] (%s) to[%s]%s (%s);\n" % (stylestr, src,
+ topath, extra,dst)
+ elif not self.options.get('straightedges',False):
+ s += " \draw [%s] %s ..%s (%s);\n" % (stylestr,
+ " .. ".join(pstrs), extra,dst)
+ else:
+ s += " \draw [%s] (%s) --%s (%s);\n" % (stylestr, src, extra,dst)
+
+ return s
+
+ def startNode(self, node):
+ return ""
+
+ def endNode(self,node):
+ return ""
+
+dimext = r"""
+^\!\s Preview:\s Snippet\s
+(?P<number>\d*)\s ended.
+\((?P<ht>\d*)\+(?P<dp>\d*)x(?P<wd>\d*)\)"""
+
+class TeXDimProc:
+ """Helper class for for finding the size of TeX snippets
+
+ Uses preview.sty
+ """
+ # Produce document
+ # Create a temporary directory
+ # Compile file with latex
+ # Parse log file
+ # Update graph with with and height parameters
+ # Clean up
+ def __init__(self,template,options):
+ self.template = template
+ self.snippets_code=[]
+ self.snippets_id=[]
+ self.options = options
+ self.dimext_re = re.compile(dimext,re.MULTILINE|re.VERBOSE)
+ pass
+ def addSnippet(self,id, code):
+ """A a snippet of code to be processed"""
+ self.snippets_id.append(id)
+ self.snippets_code.append(code)
+
+ def process(self):
+ """Process all snippets of code with TeX and preview.sty
+
+ Results are stored in the texdimlist and texdims class attributes.
+ Returns False if preprocessing fails
+ """
+ import shutil
+ if len(self.snippets_code) == 0:
+ log.warning('No labels to preprocess')
+ return True
+ self.tempdir = tempfile.mkdtemp(prefix='dot2tex')
+ log.debug('Creating temporary directroy %s' % self.tempdir)
+ self.tempfilename = os.path.join(self.tempdir,'dot2tex.tex')
+ log.debug('Creating temporary file %s' % self.tempfilename)
+ f = open(self.tempfilename,'w')
+ s = ""
+ for n in self.snippets_code:
+ s += "\\begin{preview}%\n"
+ s += n.strip()+"%\n"
+ s += "\end{preview}%\n"
+
+ f.write(self.template.replace('<<preproccode>>',s))
+ #f.flush()
+ f.close()
+ s = open(self.tempfilename,'r').read()
+ log.debug('Code written to %s\n' % self.tempfilename + s)
+ self.parseLogFile()
+ shutil.rmtree(self.tempdir)
+ log.debug('Temporary directory and files deleted')
+ if self.texdims:
+ return True
+ else:
+ return False
+ # cleanup
+
+ def parseLogFile(self):
+ logfilename = os.path.splitext(self.tempfilename)[0]+'.log'
+ tmpdir = os.getcwd()
+ os.chdir(os.path.split(logfilename)[0])
+ if self.options.get('usepdflatex',False):
+ command = 'pdflatex -interaction=nonstopmode %s' % self.tempfilename
+ else:
+ command = 'latex -interaction=nonstopmode %s' % self.tempfilename
+ log.debug('Running command: %s' % command)
+ sres = os.popen(command)
+ resdata = sres.read()
+ #log.debug('resdata: %s' % resdata)
+ errcode = sres.close()
+ log.debug('errcode: %s' % errcode)
+ f = open(logfilename,'r')
+ logdata = f.read()
+ log.debug('Logfile from LaTeX run: \n'+logdata)
+ f.close()
+ os.chdir(tmpdir)
+
+ texdimdata = self.dimext_re.findall(logdata)
+ log.debug('Texdimdata: '+ str(texdimdata))
+ if len(texdimdata)== 0:
+ log.error('No dimension data could be extracted from dot2tex.tex.')
+ self.texdims = None
+ return
+
+ c = 1/(100.0*7227)
+ c = 1.0/4736286
+ self.texdims = {}
+ self.texdimlist = [(float(i[1])*c,float(i[2])*c,float(i[3])*c) for i in texdimdata]
+ #for i in range(len(self.snippets_id)):
+ # self.texdims[self.snippets_id[i]] = self.texdimlist[i]
+ self.texdims = dict(zip(self.snippets_id,self.texdimlist))
+
+
+def cleanDotNodes(graph):
+ """Remove unnecessary data from node list"""
+ nodes = graph.get_node_list()
+ tmp = [node for node in nodes if node.name.startswith('"')]
+ for node in nodes:
+ node.name = node.name.replace('"','')
+ # There are some strange issues with what nodes are returned
+ # across different operating systems and versions of pydot, pyparsing,
+ # and Graphviz. This is just a quick fix. Need to investigate this further
+ if tmp:
+ return tmp
+ else:
+ return nodes
+
+
+# todo: Should also support graph attributes
+def setDotAttr(graph):
+ """Distribute default attributes to nodes and edges"""
+ elements = getAllGraphElements(graph,[])
+ stack = []
+ definednodes = {}
+ defattrs = {}
+ defedgeattrs = {}
+ for element in elements:
+ if isinstance(element,pydot.Node):
+ node = element
+ nodeattrs = node.__dict__
+ # is it an attribute statement? (attr_stmt)
+ if node.name == 'node':
+ # The node attributes are stored in node.__dict__. Extract all
+ # items that are not None or functions
+ defattrs.update([a for a in nodeattrs.items()
+ if (a[1] or a[1]=='') and not callable(a[1])])
+ elif node.name == 'edge':
+ defedgeattrs.update([a for a in nodeattrs.items()
+ if a[1] and not callable(a[1])])
+ continue
+ elif node.name == 'graph':
+ continue
+ else:
+ name = node.name.replace('"','')
+ if definednodes.get(name,False):
+ continue
+ #pass
+ else:
+ definednodes[name] = True
+ for key,value in defattrs.items():
+ if value=='\N':continue
+ if not node.__dict__.get(key):
+ node.__dict__[key]=value
+ elif isinstance(element,pydot.Edge):
+ edge = element
+ for key,value in defedgeattrs.items():
+ if not edge.__dict__.get(key):
+ edge.__dict__[key]=value
+ elif isinstance(element,pydot.Graph):
+ # push current set of attributes on the stack
+ stack.append((defattrs,defedgeattrs))
+ defattrs = defattrs.copy()
+ defedgeattrs = defedgeattrs.copy()
+ elif isinstance(element,EndOfGraphElement):
+ defattrs,defedgeattrs = stack.pop()
+
+
+def convertDot(data, backend='pstricks', options = {}):
+ """Convert xdot data to a backend compatible format
+
+ Input:
+ data - dot
+ backend - pstricks, pgf, eepic
+
+ """
+ graph = pydot.graph_from_dot_data(data)
+ nodes = cleanDotNodes(graph)
+ edges = graph.edge_list
+ # Detect graph type
+ if graph.graph_type == 'digraph':
+ directedgraph = True
+ else:
+ directedgraph = False
+ # process edges and nodes
+ for node in nodes:
+ pass
+
+
+def debug(filename = 'd:/pycode/algs/t.dot'):
+ dotdata = open(filename).read()
+ r = convertDot(dotdata)
+ print r
+
+
+def processCmdLine():
+ """Set up and parse command line options"""
+ usage = "Usage: %prog [options] <files>"
+ parser = OptionParser(usage)
+ parser.add_option("-f", "--format",
+ action="store", dest="format",
+ choices = ('pstricks','pgf','pst','tikz'),
+ help="Set output format to 'v' (pstrics, pgf) ", metavar="v")
+ parser.add_option('-t','--texmode', dest='texmode', default = 'verbatim',
+ choices = ('math','verbatim', 'raw'),
+ help = "Set text mode (verbatim, math, raw).")
+ parser.add_option('-d', '--duplicate', dest = 'duplicate', action='store_true',
+ default=False, help='Try to duplicate Graphviz graphics')
+ parser.add_option('-s', '--straightedges', dest = 'straightedges', action='store_true',
+ default=False, help='Force straight edges')
+ parser.add_option('--template', dest = 'templatefile', action = 'store',
+ metavar = "FILE")
+ parser.add_option('-o','--output', dest = 'outputfile', action = 'store',
+ metavar = "FILE", default='',help="Write output to FILE")
+ parser.add_option('-e','--encoding', dest = 'encoding', action = 'store',
+ choices = ('utf8','latin1'), default=DEFAULT_TEXTENCODING,
+ help="Set text encoding to utf8 or latin1")
+ parser.add_option('-V','--version', dest = 'printversion', action='store_true',
+ help="Print version information and exit", default=False),
+ parser.add_option('-w','--switchdraworder', dest = 'switchdraworder',
+ action="store_true", help = "Switch draw order", default=False),
+ parser.add_option('-p','-c','--preview', '--crop', dest = 'crop', action = 'store_true',
+ help="Use preview.sty to crop graph", default=False),
+ parser.add_option('--margin', dest = 'margin', action = 'store',
+ help="Set preview margin", default="0pt"),
+ parser.add_option('--docpreamble', dest = 'docpreamble', action = 'store',
+ help="Insert TeX code in document preamble", metavar="TEXCODE"),
+ parser.add_option('--figpreamble', dest = 'figpreamble', action = 'store',
+ help="Insert TeX code in figure preamble", metavar="TEXCODE"),
+ parser.add_option('--figpostamble', dest = 'figpostamble', action = 'store',
+ help="Insert TeX code in figure postamble", metavar="TEXCODE"),
+ parser.add_option('--graphstyle', dest = 'graphstyle', action = 'store',
+ help="Insert graph style", metavar="STYLE"),
+ parser.add_option('--gvcols', dest='gvcols', action ="store_true",
+ default=False, help="Include gvcols.tex"),
+ parser.add_option('--figonly', dest='figonly', action ="store_true",
+ help="Output graph with no preamble", default=False)
+ parser.add_option('--codeonly', dest='codeonly', action ="store_true",
+ help="Output only drawing commands", default=False)
+ parser.add_option('--styleonly', dest='styleonly', action ="store_true",
+ help="Use style parameter only", default=False)
+ parser.add_option('--debug',dest='debug', action="store_true",
+ help="Show additional debugging information", default=False)
+ parser.add_option('--preproc', dest='texpreproc', action="store_true",
+ help = 'Preprocess graph through TeX', default=False)
+ parser.add_option('--alignstr',dest='alignstr',action='store')
+ parser.add_option('--valignmode', dest='valignmode', default = 'center',
+ choices = ('center','dot'),
+ help = "Set vertical alginment mode (center, dot).")
+ parser.add_option('--nominsize', dest='nominsize', action ="store_true",
+ help="No minimum node sizes", default=False)
+ parser.add_option('--usepdflatex', dest='usepdflatex', action ="store_true",
+ help="Use PDFLaTeX for preprocessing", default=False)
+ parser.add_option('--tikzedgelabels', dest='tikzedgelabels', action ="store_true",
+ help="Let TikZ place edge labels", default=False)
+ parser.add_option('--nodeoptions', dest = 'nodeoptions', action = 'store',
+ help="Set options for nodes", metavar="OPTIONS"),
+ parser.add_option('--edgeoptions', dest = 'edgeoptions', action = 'store',
+ help="Set options for edges", metavar="OPTIONS"),
+ parser.add_option('--runtests',dest='runtests',
+ help="Run testes", action="store_true",default=False)
+
+ parser.add_option("--prog", action="store", dest="prog", default='dot',
+ choices = ('dot','neato','circo','fdp','twopi'),
+ help="Use v to process the graph", metavar="v"),
+ parser.add_option('--autosize',dest='autosize',
+ help="Preprocess graph and then run Graphviz", action="store_true",default=False)
+
+ #parser.add_option("--var", action="append", dest="uservars")
+
+ (options, args) = parser.parse_args()
+
+ return options,args,parser
+
+def _runtests():
+ import doctest
+ doctest.testmod()
+
+
+def printVersionInfo():
+ print "Dot2tex version % s" % __version__
+
+def main():
+ import platform
+ options, args,parser = processCmdLine()
+ if options.runtests:
+ log.warning('running tests')
+ _runtests()
+ sys.exit(0)
+
+ if options.debug:
+ # initalize log handler
+ hdlr = logging.FileHandler('dot2tex.log')
+ log.addHandler(hdlr)
+ formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
+ hdlr.setFormatter(formatter)
+ log.setLevel(logging.DEBUG)
+ nodebug = False
+ else:
+ nodebug = True
+
+ log.info('------- Start of run -------')
+ log.info("Dot2tex version % s" % __version__)
+ log.info("System information:\n"
+ " Python: %s \n"
+ " Platform: %s\n"
+ " Pydot: %s\n"
+ " Pyparsing: %s",
+ sys.version_info, platform.platform(),
+ pydot.__version__,pydot.dot_parser.pyparsing_version)
+ log.info('dot2tex called with: %s' % sys.argv)
+ log.info('Program started in %s' % os.getcwd())
+
+ if options.printversion:
+ #print options.hest
+ printVersionInfo()
+ sys.exit(0)
+ if len(args) == 0:
+ log.info('Data read from standard input')
+ dotdata = sys.stdin.readlines()
+ elif len(args) == 1:
+ try:
+ dotdata = open(args[0], 'rU').readlines()
+ except:
+ log.error("Could not open input file %s" % args[0])
+ sys.exit(1)
+ log.info('Data read from %s' % args[0])
+
+ # I'm not quite sure why this is necessary, but some files
+ # produces data with line endings that confuses pydot/pyparser.
+ # Note: Whitespace at end of line is sometimes significant
+ log.debug('Input data:\n'+"".join(dotdata))
+ lines = [line for line in dotdata if line.strip()]
+ dotdata = "".join(lines)
+ #dotdata = convert_line_endings(dotdata,0)
+ #optparse.check_choice()
+
+ # check for output format attribute
+ fmtattr = re.findall(r'd2toutputformat=(.*?);',dotdata)
+ extraoptions = re.findall(r'd2toptions\s*=\s*"(.*?)"\s*;?',dotdata)
+ if fmtattr:
+ log.info('Found outputformat attribute: %s',fmtattr[0])
+ gfmt = fmtattr[0]
+ else:
+ gfmt = None
+ if extraoptions:
+ #log.warning('Found d2toptions attribute in graph: %s',extraoptions[0])
+ (options, args) = parser.parse_args(extraoptions[0].split(),options)
+ if options.debug and nodebug:
+ # initalize log handler
+ hdlr = logging.FileHandler('dot2tex.log')
+ log.addHandler(hdlr)
+ formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
+ hdlr.setFormatter(formatter)
+ log.setLevel(logging.DEBUG)
+ nodebug = False
+
+
+ output_format = options.format or gfmt or DEFAULT_OUTPUT_FORMAT
+ options.format = output_format
+
+ if output_format in ('pstricks', 'pst'):
+ conv = Dot2PSTricksConv(options.__dict__)
+ elif output_format == 'pgf':
+ conv = Dot2PGFConv(options.__dict__)
+ elif output_format == 'tikz':
+ conv = Dot2TikZConv(options.__dict__)
+ else:
+ log.error("Unknown output format %s" % options.format)
+ sys.exit(1)
+ try:
+ s = conv.convert(dotdata)
+ log.debug('Output:\n'+s)
+ if options.autosize:
+ conv.dopreproc = False
+ s = conv.convert(s)
+ log.debug('Output:\n'+s)
+ if options.outputfile:
+ f = open(options.outputfile, 'w')
+ f.write(s)
+ f.close()
+ else:
+ print s
+ #if options.texpreproc:
+ # print conv.preproc.writeTempFile()
+ except SystemExit:
+ pass
+ except Exception:
+ #log.error("Could not convert the xdot input.")
+ log.exception('Failed to process input')
+
+ log.info('------- End of run -------')
+
+
+
+if __name__ == '__main__':
+ main()
+
+
+