class TestScope(TestCase):
    def setUp(self):
        self.cs = CascadingStyles({"font":"arial","font-size":"12pt"})
        
    def testRemoveScope(self):
        self.cs.appendScope()
        self.cs["font"] = "newfont"
        self.cs["font"] == "newfont"
class TestScope(TestCase):
    def setUp(self):
        self.cs = CascadingStyles({"font": "arial", "font-size": "12pt"})

    def testRemoveScope(self):
        self.cs.appendScope()
        self.cs["font"] = "newfont"
        self.cs["font"] == "newfont"
class TestDefaults(TestCase):
    def setUp(self):
        self.cs = CascadingStyles({"font": "arial", "font-size": "12pt"})

    def testNone(self):
        self.assertEqual(str(self.cs), "")

    def testRemoveScope(self):
        self.cs.appendScope()
        self.cs["font"] = "newfont"
        self.cs.popScope()
        self.assertEqual(str(self.cs), "")

    def testIgnoreDefault(self):
        self.cs.appendScope()
        self.cs["font"] = "newfont"
        self.cs.appendScope()
        self.cs["font"] = "arial"
        self.assertEqual(str(self.cs), "")
class TestDefaults(TestCase):
    def setUp(self):
        self.cs = CascadingStyles({"font":"arial","font-size":"12pt"})
        
    def testNone(self):
        assert str(self.cs) == ""
        
    def testRemoveScope(self):
        self.cs.appendScope()
        self.cs["font"] = "newfont"
        self.cs.popScope()
        assert str(self.cs) == ""
        
        
    def testIgnoreDefault(self):
        self.cs.appendScope()
        self.cs["font"] = "newfont"
        self.cs.appendScope()
        self.cs["font"] = "arial"
        assert str(self.cs) == ""
Exemple #5
0
class TargetSvg(object):
    def __init__(self):
        self.svg_dom = None
        self.svg_current_layer = None
        self.svg_current_font  = ""
        self.svg_def = None
        self.style = None
        self.reset()
       
    def reset(self):
        self.svg_dom = xml.dom.minidom.Document()
        self.svg_dom.doctype = ""
        svg_tag = self.svg_dom.createElement("svg")
        svg_tag.setAttribute("xmlns","http://www.w3.org/2000/svg")
        svg_tag.setAttribute("xmlns:xlink","http://www.w3.org/1999/xlink")
        self.svg_dom.appendChild(svg_tag)
        def_tag = self.svg_dom.createElement("defs")
        svg_tag.appendChild(def_tag)
        self.svg_def = def_tag
        
        # set of required macros
        self.required_defs = set()

        self.style = CascadingStyles()
        self.style.appendScope()
        self.style["fill"]="#fff"
        self.style["stroke"]="#000000"
        self.style["stroke-width"]="1.000000px"

        graphic_tag = self.svg_dom.createElement("g")
        graphic_tag.setAttribute("style",str(self.style))
        svg_tag.appendChild(graphic_tag)
        self.svg_current_layer = graphic_tag

    @property
    def svg(self):
        """Return the svg document"""
        return self.svg_dom.toprettyxml(encoding='utf-8')

    def mkHex(self, s):
        # s is a string of a float
        h = "%02x"%(int(min(float(s)*256, 255)))
        return h

    def extract_colour(self,col):
        # only gets rgb values (ignores a)
        return "".join( [self.mkHex(col["r"]), 
                        self.mkHex(col["g"]), 
                        self.mkHex(col["b"])] )

    def setGraffleFont(self, font):
        if font is None: return
        fontstuffs = []
        font_col = "000000"
        
        if font.get("Color") is not None:
            grap_col = font.get("Color")
            try:
                font_col = self.extract_colour(grap_col)
            except:
                font_col = "000000"
        fontstuffs.append("fill:#%s"%font_col)
        fontstuffs.append("stroke:#%s"%font_col)
        fontstuffs.append("stroke-width:0.1px")
            
        fontfam = font.get("Font")
        if fontfam is not None:
            if fontfam == "LucidaGrande":
                fontfam = "Luxi Sans"
            elif fontfam == "Courier":
                fontfam = "Courier New"
            elif fontfam == "GillSans":
                fontfam = "Arial Narrow"
            fontstuffs.append("font-family: %s"%fontfam)
            
        size = font.get("Size")
        if size is not None:
            fontstuffs.append("font-size:%dpx"%int(size) )
        
        self.svg_current_font = ";".join(fontstuffs)
        
    def addLayer(self, graffleInterpreter, GraphicsList):
        current_layer = self.svg_current_layer
        self.style.appendScope()
        g_emt = self.svg_dom.createElement("g")
        g_emt.setAttribute("style",str(self.style))
        current_layer.appendChild(g_emt)
        self.svg_current_layer = g_emt
        graffleInterpreter.itterateGraffleGraphics(GraphicsList)
        self.style.popScope()
        self.svg_current_layer = current_layer
    
    def addAdjustableArrow(self, bounds, graphic,**opts):
        x,y,width,height = [float(a) for a in bounds]
        ratio = float(graphic["ShapeData"]["ratio"])
        neck = float(graphic["ShapeData"]["width"])
        neck_delta = height*(1-ratio)/2
        self.addPath([[x,y+neck_delta], [x+width-neck,y+neck_delta], [x+width-neck,y],
                          [x+width,y+height/2], [x+width-neck,y+height], [x+width-neck,y+height-neck_delta],
                          [x,y+height-neck_delta]],closepath=True,**opts) 
                             

    def addDiamond(self,bounds,**opts):
        x, y, width, height = [float(a) for a in bounds]
        xmiddle = x+width/2
        ymiddle = y+height/2
        self.addPath([[x,ymiddle], [xmiddle,y+height], [x+width,ymiddle], [xmiddle,y]],closepath=True,**opts)

    def addPath(self, pts, **opts):
        # do geometry mapping here
        mypts = pts
        if opts.get("HFlip",False):
            mypts = geom.h_flip_points(mypts)
        if opts.get("VFlip",False):            
            mypts = geom.v_flip_points(mypts)
        if opts.get("Rotation") is not None:
            mypts = geom.rotate_points(mypts,opts["Rotation"])
            
        ptStrings = [",".join([str(b) for b in a]) for a in mypts]
        line_string = "M %s"%ptStrings[0] + " ".join(" L %s"%a for a in ptStrings[1:] )
        if opts.get("closepath",False):
            line_string = line_string + " z"
        path_tag = self.svg_dom.createElement("path")
        path_tag.setAttribute("id", opts.get("id",""))
        path_tag.setAttribute("style", str(self.style.scopeStyle()))
        path_tag.setAttribute("d", line_string)
        self.svg_current_layer.appendChild(path_tag)
        
    def addHorizontalTriangle(self, bounds, **opts):
        """Graffle has the "HorizontalTriangle" Shape"""
        x,y,width,height = [float(a) for a in bounds]
        self.addPath([[x,y],[x+width,y+height/2], [x,y+height]], \
                        closepath=True, **opts)
                        
    def addImage(self, bounds, **opts):
        """SVG viewers should support images - unfortunately many don't :-("""
        x,y,width,height = [float(a) for a in bounds]
        image_tag = self.svg_dom.createElement("image")
        image_tag.setAttribute("x", str(x))
        image_tag.setAttribute("y", str(y))
        image_tag.setAttribute("width", str(width))
        image_tag.setAttribute("height", str(height))
        image_tag.setAttribute("xlink:href", str(opts.get("href","")))
        image_tag.setAttribute("style", str(self.style.scopeStyle()))
        self.svg_current_layer.appendChild(image_tag)
        
    def addRightTriangle(self,  bounds, **opts):
        """Graffle has the "RightTriangle" Shape"""
        x,y,width,height = [float(a) for a in bounds]
        self.addPath( [[x,y],[x+width,y+height], [x,y+height]], \
                        closepath=True, **opts)

    def addVerticalTriangle(self,  bounds, **opts):
        """Graffle has the "RightTriangle" Shape"""
        x,y,width,height = [float(a) for a in bounds]
        self.addPath( [[x,y],[x+width,y], [x+width/2,y+height]], \
                        closepath=True, **opts)
            
    def addRect(self,  **opts):
        """Add an svg rect"""
        if opts is None:
            opts = {}
        rect_tag = self.svg_dom.createElement("rect")
        rect_tag.setAttribute("id",opts.get("id",""))
        rect_tag.setAttribute("width",str(opts["width"]))
        rect_tag.setAttribute("height",str(opts["height"]))
        rect_tag.setAttribute("x",str(opts.get("x","0")))
        rect_tag.setAttribute("y",str(opts.get("y","0")))
        if opts.get("rx") is not None:
            rect_tag.setAttribute("rx",str(opts["rx"]))
            rect_tag.setAttribute("ry",str(opts["ry"]))
            
        rect_tag.setAttribute("style", str(self.style.scopeStyle()))
        self.svg_current_layer.appendChild(rect_tag)
        
    def addEllipse(self,  bounds, **opts):
        c = [bounds[i] + (bounds[i+2]/2.) for i in [0,1]] # centre of circle
        rx = bounds[2]/2.
        ry = bounds[3]/2.
        circle_tag = self.svg_dom.createElement("ellipse")
        circle_tag.setAttribute("id", opts.get("id",""))
        circle_tag.setAttribute("style", str(self.style.scopeStyle()))
        circle_tag.setAttribute("cx", str(c[0]))
        circle_tag.setAttribute("cy", str(c[1]))
        circle_tag.setAttribute("rx", str(rx))
        circle_tag.setAttribute("ry", str(ry))
        self.svg_current_layer.appendChild(circle_tag)

    def addCloud(self,bounds,**opts):
        """Add a cloud element"""
        self.required_defs.add("network_cloud")
        x, y, dx, dy = bounds
        cloud_tag = self.svg_dom.createElement("g")
        cloud_tag.setAttribute("id", opts.get("id",""))
        cloud_tag.setAttribute("style", str(self.style.scopeStyle()))
        cloud_tag.setAttribute("transform","translate(%f,%f) scale(%f,%f)" % (x,y,float(dx)/500.0,float(dy)/500.0))
        p = xml.dom.minidom.parseString("<use xmlns:xlink='http://www.w3.org/1999/xlink' xlink:href='#network_cloud' />")
        def_node = p.childNodes[0]
        cloud_tag.appendChild(def_node)
        self.svg_current_layer.appendChild(cloud_tag)

    def addText(self,**opts):
        """Add an svg text element"""
        text_tag = self.svg_dom.createElement("text")
        text_tag.setAttribute("id",str(opts.get("id",""))+"_text")
        text_tag.setAttribute("x",str(opts.get("x","0")))
        text_tag.setAttribute("y",str(opts.get("y","0")))
#        text_tag.setAttribute("dominant-baseline","mathematical")
        text_tag.setAttribute("style", self.svg_current_font)
        self.svg_current_layer.appendChild(text_tag)
        
        
        # Generator
        lines = extractRTFString(opts["rtftext"])
        font_info =  opts.get("fontinfo",None)
        if font_info is not None:
            font_height = int(font_info.get("Size"))
        else:
            font_height = 12
        total_height = 0.0
        for span in lines:
            total_height += float(span["style"].get("font-size","%.1fpx"%font_height)[:-2])
        y_diff = float(opts.get("y", "12.0")) + opts.get("height",0)/2 -total_height/2
        line_id=opts["id"]
        linenb = 0
        for span in lines:
            y_diff+= float(span["style"].get("font-size","%.1fpx"%font_height)[:-2])
            linenb+=1
            opts["id"]=str(line_id)+"_line"+str(linenb)
            self.addLine(text_tag,text = span["string"], style = span["style"],\
                    y_pos=y_diff, line_height =font_height, **opts)
        
    def addLine(self,textnode, **opts):
        """Add a line of text"""
        tspan_node = self.svg_dom.createElement("tspan")
        tspan_node.setAttribute("id",opts.get("id",""))
        align = opts.get("style", {"text-align":"left"}).get("text-align", "left")
        x = opts.get("x",0)
        if align=="center":
            tspan_node.setAttribute("text-anchor","middle")
            x+=opts.get("width", 0)/2
        elif align == "right":
            x+=opts.get("width", 0)
        tspan_node.setAttribute("x",str(x))
        y_pos = float(opts.get("y_pos",0))
        tspan_node.setAttribute("y",str(y_pos))
        if (opts.get("style") is not None) and len(opts.get("style"))>0:
            thestyle = opts.get("style")
            for (k,v) in thestyle.items():
                tspan_node.setAttribute(k,v)
        actual_string = self.svg_dom.createTextNode(opts.get("text"," "))
        tspan_node.appendChild(actual_string)
        textnode.appendChild(tspan_node)

    def add_requirements(self):
        for required in self.required_defs:
            if required.startswith("Arrow1Lend"):
                (shape,  color, width) = required.split( "_")
                scale=1.0/((float(width[0:-2])-1.0)/20.0+1.0)
                p = xml.dom.minidom.parseString("""
                <defs><marker
                   orient='auto'
                   refY='0.0'
                   refX='0.0'
                   id='%(id)s'
                   style='overflow:visible;'>
                  <path
                     id='path_%(id)s'
                     d='M -5,0.0 L -5,-2.0 L 0.0,0.0 L -5,2.0 z '
                     style='fill:#%(color)s;fill-rule:evenodd;stroke:#%(color)s;stroke-width:1.0px;marker-start:none;'
                     transform='scale(%(scale)f)' />
                </marker></defs>"""%{"id":required, "color":color,  "scale":scale})
                def_node = p.childNodes[0]
                for node in def_node.childNodes:
                    self.svg_def.appendChild(node)
            if required.startswith("Arrow1Lstart"):
                (shape,  color, width) = required.split( "_")
                scale=1.0/((float(width[0:-2])-1.0)/20.0+1.0)
                p = xml.dom.minidom.parseString("""
                <defs><marker
                   orient='auto'
                   refY='0.0'
                   refX='0.0'
                   id='%(id)s'
                   style='overflow:visible'>
                  <path
                     id='path_%(id)s'
                     d='M 5,0.0 L 5.0,-2.0 L 0.0,0.0 L 5.0,2.0 z'
                     style='fill:#%(color)s;fill-rule:evenodd;stroke:#%(color)s;stroke-width:1.0px;marker-start:none'
                     transform='scale(%(scale)f)'/>
                </marker></defs>"""%{"id":required, "color":color,  "scale":scale})
                def_node = p.childNodes[0]
                for node in def_node.childNodes:
                    self.svg_def.appendChild(node)

        if "Arrow2Lend" in self.required_defs:
            # TODO
            p = xml.dom.minidom.parseString("""
            <defs><marker
               orient='auto'
               refY='0.0'
               refX='0.0'
               id='Arrow2Lend'
               overflow='visible'
               stroke='currentColor'>
              <path
                 id='Arrow2Lend'
                 d='M 10.0,-2.0 L 0.0,0.0 L 10.0,2.0'
                 style='fill:none;stroke:#000000;stroke-width:1.0px;marker-start:none' />
            </marker></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)        

        if "Arrow2Lstart" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><marker
               orient='auto'
               refY='0.0'
               refX='0.0'
               id='Arrow2Lstart'
               overflow='visible'
               stroke='currentColor'>
              <path
                 id='pathStickArrow'
                 d='M -10.0,-2.0 L 0.0,0.0 L -10.0,2.0'
                 style='fill:none;stroke:#000000;stroke-width:1.0px;marker-start:none'/>
            </marker></defs>""")           
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)
                
        if "DropShadow" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><filter id='DropShadow' filterRes='100' x='0' y='0'>
               <feGaussianBlur stdDeviation='3' result='MyBlur'/>
               <feOffset in='MyBlur' dx='2' dy='4' result='movedBlur'/>
               <feMerge>
                   <feMergeNode in='movedBlur'/>
                   <feMergeNode in='SourceGraphic'/>
               </feMerge>
          </filter></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)
                
        if "CrowBall" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><marker
            refX='0'
            refY='0'
            orient='auto'
            id='mCrowBall'
            style='overflow:visible'>
            <path d='M 0.0,2.5 L 7.5,0.0 L 0.0,-2.5' 
             style='stroke:#000;stroke-width:1.0px;marker-start:none;fill:none;' />
            <circle cx='10' cy='0' r='2.5' style='stroke-width:1px; stroke: #000; fill:none;'/>
            </marker></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)
                
        if "Bar" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><marker
            refX='0'
            refY='0'
            orient='auto'
            id='mBar'
            style='overflow:visible'>
            <path d='M -7.5,-2.5 L -7.5,2.5' 
             style='stroke:#000;stroke-width:1.0px;marker-start:none;fill:none;' />
            </marker></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)

        if "network_cloud" in self.required_defs:
            ''' cloud shape 500x500 '''
            p = xml.dom.minidom.parseString("""
            <defs><g id='network_cloud'>
            <path d='M 127.5 106.25 C 127.5 106.25 126.25 18.7501 231.25 45 C 336.25 71.25 317.5 115 318.75 115 C 320 115 300 61.25 380 51.25 C 452.5 57.5 486.25 71.25 482.5 117.5 C 478.75 163.75 443.75 175 443.75 175 C 443.75 175 507.5 181.25 490 275 C 462.5 340 462.5 333.75 398.75 341.25 C 370 330 368.75 320 368.75 320 C 368.75 320 421.25 368.75 342.5 400 C 253.75 423.75 242.5 402.5 205 391.25 C 168.75 368.75 176.25 341.25 176.25 341.25 C 176.25 341.25 198.75 387.5 122.5 396.25 C 46.25 405 17.5 387.5 3.75 316.25 C 2.08616e-06 262.5 67.5 257.5 67.5 257.5 C 67.5 257.5 26.25 258.75 15 231.25 C 3.75 203.75 -3.75 167.5 30 118.75 C 92.5 60 135 98.75 127.5 106.25 z '/>
            </g></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)

            
    def setGraffleStyle(self, style):        
        if style.get("fill") is not None:
            fill = style.get("fill")
            if fill.get("Draws","") == "NO":
                # don't display
                self.style["fill"]="none"
            else:
                grap_col = fill.get("Color")
                if grap_col is not None:
                    fill_col = self.extract_colour(grap_col)
                    self.style["fill"]="#%s"%fill_col
                
        if style.get("stroke") is not None:
            stroke = style.get("stroke")
            if stroke.get("Draws","") == "NO":
                self.style["stroke"]="none"
            else:
            
                grap_col = stroke.get("Color",{"r":0.,"g":0.,"b":0.})
                if grap_col is not None:
                    stroke_col = self.extract_colour(grap_col)
                    self.style["stroke"]="#%s"%stroke_col

            if stroke.get("Width") is not None:
                width = stroke["Width"]
                self.style["stroke-width"]="%fpx"%float(width)

            if stroke.get("HeadArrow") is not None:
                headarrow = stroke["HeadArrow"]
                marker_end = "none"
                headscale = stroke.get("HeadScale", 1.0)
                if headarrow == "FilledArrow":
                    marker_end = "Arrow1Lend_" + self.style["stroke"] [1:] + "_" + "%fpx"%(float(self.style["stroke-width"][0:-2])*headscale)
                    self.style["marker-end"]="url(#%s)"%marker_end
                    self.required_defs.add(marker_end)
                elif headarrow == "StickArrow":
                    self.style["marker-end"]="url(#Arrow2Lend)"
                    self.required_defs.add("Arrow2Lend")
                elif headarrow == "Bar":
                    #TODO
                    self.style["marker-end"]="url(#mBar)"
                    self.required_defs.add("Bar")                    
                elif headarrow == "0":
                    self.style["marker-end"] = "none"
                else:
                    print "unknown HeadArrow "+ headarrow
                    self.style["marker-end"]="url(#Arrow2Lend)"
                    self.required_defs.add("Arrow2Lend")

            if stroke.get("TailArrow") is not None:
                tailarrow = stroke["TailArrow"]
                tailscale = stroke.get("TailScale",1.0)
                if tailarrow == "FilledArrow":
                    marker_start = "Arrow1Lstart_" + self.style["stroke"] [1:] + "_" + "%fpx"%(float(self.style["stroke-width"][0:-2])*tailscale)
                    self.style["marker-start"]="url(#%s)"%marker_start
                    self.required_defs.add(marker_start)
                elif tailarrow =="StickArrow":
                    self.style["marker-end"]="url(#Arrow2Lstart)"
                    self.required_defs.add("Arrow2Lstart")
                elif tailarrow == "CrowBall":
                    self.style["marker-start"]  = "url(#mCrowBall)"
                    self.required_defs.add("CrowBall")
                    
                elif tailarrow == "0":
                    self.style["marker-start"]="none"
                else: 
                    print "unknown TailArrow "+ headarrow
                    self.style["marker-end"]="url(#Arrow2Lstart)"
                    self.required_defs.add("Arrow2Lstart")

            if stroke.get("Pattern") is not None:
                pattern = stroke["Pattern"]
                if pattern == 1:
                    self.style["stroke-dasharray"]="3 3"
                elif pattern == 2:
                    self.style["stroke-dasharray"]="5 5"
                else:
                    print "unknown pattern " + str(pattern)
                    self.style["stroke-dasharray"]="1 1"

        if style.get("shadow",{}).get("Draws","NO") != "NO":
            # for some reason graffle has a shadow by default
            self.required_defs.add("DropShadow")
            self.style["filter"]="url(#DropShadow)"
Exemple #6
0
def extractRTFString(s):
    """Extract a string and some styling info"""
    bracket_depth = 0
    instruction = False
    inst_code = ""

    ftable = FontTable()

    # The string being generated:
    std_string = ""
    style = CascadingStyles()
    # Want to set these as defaults even if not specified
    style.appendScope()
    style["fill"]="#000000"
    """TODO: 
     extract styling 
     
     e.g. 
     {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf460
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural

\f0\fs28 \cf0 Next ads are represented in a book-ended carousel on end screen}
    """

    def do_instruction(inst_code, i):
        if inst_code == "b":
            style["font-weght"] = "bold"
        if inst_code == "ql":
            style["text-align"] = "left"
        elif inst_code == "qr":
            style["text-align"] = "right"
        elif inst_code == "qj":
            style["text-align"] = "justify"
        elif inst_code == "qc":
            style["text-align"] = "center"
        elif inst_code == "fonttbl":
            i = ftable.parseTable(s,i)
        elif inst_code[0]=="f" and inst_code[1:].isdigit():
            # Font looked up in font table
            font = ftable.fonts.get(int(inst_code[1:]),{})
            for k,v in font.items():
                if k != "":
                    style[k] = v
        elif inst_code[:2]=="fs" and isint(inst_code[2:]):
            # font size - RTF specifies half pt sizes
            style["font-size"] = "%.1fpt"%(float(inst_code[2:])/2.)
        elif inst_code[:2]=="cf" and isint(inst_code[2:]):
            # font colour is enytry int(inst_code[2:]) in the colour table :-(
            # font is chosen from font table using fN (N\in\int)
            pass
        return i
    i = -1
    while i < len(s)-1:
        i += 1
        c = s[i]
        if c == "{":
            bracket_depth +=1
            style.appendScope()
        elif c == "}":
            if std_string != "":
                yield {"string":std_string, "style":str(style)}
                std_string = ""
            style.popScope()
            bracket_depth -=1
        elif c == "\\":
            if len(inst_code) > 0:
                i = do_instruction(inst_code, i)
            instruction = True
            inst_code = ""
        
        if instruction:
            if c == " ":
                instruction = False
                i = do_instruction(inst_code, i)
            elif c == "\n":
                instruction = False
                if inst_code == "":
                    # new line so yield
                    yield {"string":std_string, "style":str(style)}
                    std_string = ""
                else:
                    i = do_instruction(inst_code, i)
            elif c != "\\":
                inst_code += c

        else:
            if bracket_depth == 1:
                if not c in "{}\\\n\r":
                    # those characters are escaped
                    std_string += c
    style.popScope()
Exemple #7
0
class GraffleParser(object):
    g_dom = None
    svg_dom = None
    svg_current_layer = None
    svg_current_font  = ""
    svg_def = None
    
    def __init__(self):
        self.svg_dom = xml.dom.minidom.Document()
        self.svg_dom.doctype = ""
        svg_tag = self.svg_dom.createElement("svg")
        svg_tag.setAttribute("xmlns","http://www.w3.org/2000/svg")
        svg_tag.setAttribute("xmlns:xlink","http://www.w3.org/1999/xlink")
        self.svg_dom.appendChild(svg_tag)
        def_tag = self.svg_dom.createElement("defs")
        svg_tag.appendChild(def_tag)
        self.svg_def = def_tag
        
        # set of required macros
        self.required_defs = set()
        
        self.style = CascadingStyles()
        self.style.appendScope()
        self.style["fill"]="#fff"
        self.style["stroke"]="#000000"

        graphic_tag = self.svg_dom.createElement("g")
        graphic_tag.setAttribute("style",str(self.style))
        svg_tag.appendChild(graphic_tag)
        self.svg_current_layer = graphic_tag
                
        
    @property
    def svg(self):
        """Return the svg document"""
        return self.svg_dom.toprettyxml()
        
    def walkGraffle(self, xmlstr, **kwargs):
        """Walk over the file"""
        self.g_dom = xml.dom.minidom.parseString(xmlstr)
        
        self.walkGraffleDoc(self.g_dom, **kwargs)
        self.svg_add_requirements()
        
        
    def walkGraffleDoc(self, parent, page = 0):
        # want to pass this around like a continuation
        cont = nodeListGen(parent.childNodes)
        i = 0
        mydict = None
        for e in cont:
            if e.nodeType == e.DOCUMENT_TYPE_NODE:
                pass
            localname = e.localName
            
            if localname == "plist":
                # Apple's main container
                self.walkGraffleDoc(e,page)
                
            if localname == "dict":
                mydict = self.ReturnGraffleDict(e)

        if mydict is not None:
            # Extract file information
            self.fileinfo = fileinfo.FileInfo(mydict)
            # Graffle lists it's image references separately
            self.imagelist = mydict.get("ImageList",[])
            # Sometimes have multiple sheets
            if mydict.get("Sheets") is not None:
                self.extractPage(mydict["Sheets"][page])
            else:
                self.extractPage(mydict)
                
                
    def extractPage(self, grafflenodeasdict):
        mydict = grafflenodeasdict
        
        if self.fileinfo.fmt_version >= 6:
            # Graffle version 6 has a background graphic
            background = mydict["BackgroundGraphic"]
            # draw this 
            self.svgItterateGraffleGraphics([background])
        elif self.fileinfo.fmt_version < 6:
            # Version 5 has a CanvasColor property instead
            colour = mydict.get("CanvasColor")
            if colour is not None:
                sty = {}
                
                # We have to guess the document's dimensions from the print size
                # - these numbers appear to match up with the background size in 
                #  version 6.
                origin = parseCoords(mydict.get("CanvasOrigin","{0,0}"))
                print_info = self.fileinfo.printinfo
                
                paper_size = print_info.paper_size
                        
                Lmargin = print_info.left_margin
                Rmargin = print_info.right_margin
                Tmargin = print_info.top_margin
                Bmargin = print_info.bottom_margin
                
                x, y   = origin
                width  = paper_size[0] - Lmargin - Rmargin
                height = paper_size[1] - Bmargin - Tmargin
                self.svg_addRect(self.svg_current_layer,
                                        x = x,
                                        y = y,
                                        width = width,
                                        height = height,
                                        rx=None,
                                        ry=None)
        
        graphics = mydict["GraphicsList"]
        self.svgItterateGraffleGraphics(graphics)
        
    def ReturnGraffleNode(self, parent):
        """Return a python representation of the 
           node passed"""
        if parent.nodeType == parent.TEXT_NODE:
            return parent.wholeText
        elif parent.localName == "dict":
            return self.ReturnGraffleDict(parent)
        elif parent.localName == "array":
            return self.ReturnGraffleArray(parent)
        elif parent.localName == "true":
            return True
        elif parent.localName == "false":
            return False
        elif parent.localName in ["string","real","integer"]:
            return self.ReturnGraffleNode(parent.firstChild)
        return parent.nodeType
        
    def ReturnGraffleDict(self, parent):
        """Graffle has dicts like
            <dict>
                <key />
                <integer />
                <key />
                <string />
            </dict>
            - pass the <dict> node to this method
        """
        retdict = {}
        cont = nodeListGen(parent.childNodes)
        key, val = None, None
        for e in cont:
            if e.nodeType == e.TEXT_NODE:
                continue
            localname = e.localName
            if localname == "key":
                key = self.ReturnGraffleNode(e.firstChild)
            else:
                val = self.ReturnGraffleNode(e)
                retdict[key] = val
        return retdict
        
    def ReturnGraffleArray(self, parent):
        """Graffle has arrays like
            <array>
                <string />
                <string />
            </array>
            - pass the <array> node to this method
        """
        retlist = []
        cont = nodeListGen(parent.childNodes)
        for e in cont:
            if e.nodeType == e.TEXT_NODE:
                continue
            retlist.append(self.ReturnGraffleNode(e))
        return retlist
        
        
    def extractMagnetCoordinates(self,mgnts):
        pts = [parseCoords(a) for a in mgnts]
        return pts
        
    def extractBoundCOordinates(self,bnds):
        bnds = bnds[1:-1].strip()
        bnds = bnds.split(",")
        coords = []
        for bnd in bnds:
            bnd = bnd.replace("{","")
            bnd = bnd.replace("}","")
            coords.append(float(bnd))

        return coords

                
    def svgItterateGraffleGraphics(self,GraphicsList):
        """parent should be a list of """
        for graphics in GraphicsList:
            # Styling
            self.style.appendScope()
            if graphics.get("Style") is not None:
                self.svgSetGraffleStyle(graphics.get("Style"))
            
            cls = graphics["Class"]
            if cls == "SolidGraphic":
                # used as background - add a 
                shallowcopy = {"Shape":"Rectangle"}
                shallowcopy.update(graphics)
                self.svgAddGraffleShapedGraphic(shallowcopy)
                
            elif cls == "ShapedGraphic":
                try:
                    self.svgAddGraffleShapedGraphic(graphics)
                except:
                    raise
                    print "could not show shaped graphic"
                
            elif cls == "LineGraphic":
                pts = self.extractMagnetCoordinates(graphics["Points"])
                self.style["fill"] = "none"
                if graphics.get("OrthogonalBarAutomatic") == False:
                    bar_pos = graphics.get("OrthogonalBarPosition")
                    if bar_pos is not None:
                        # Decide where to place the orthogonal position
                        
                        bar_pos = float(bar_pos)
                        """
                        # This isn't right
                        out_pts = []
                        i = 0
                        while i < len(pts) - 1:
                            p1 = pts[i]
                            p2 = pts[i+1]
                            newpt = [p1[0] + bar_pos, p1[1]]
                            out_pts.append(p1)
                            out_pts.append(newpt)
                            out_pts.append(p2)
                            i+=2
                        pts = out_pts
                        """
                        
                    
                self.svg_addPath(self.svg_current_layer, pts)
                
            elif cls == "TableGroup":
                # In Progress
                table_graphics = graphics.get("Graphics")
                if table_graphics is not None:
                    current_layer = self.svg_current_layer
                    self.style.appendScope()
                    g_emt = self.svg_dom.createElement("g")
                    g_emt.setAttribute("style",str(self.style))
                    current_layer.appendChild(g_emt)
                    self.svg_current_layer = g_emt
                    
                    self.svgItterateGraffleGraphics(reversed(table_graphics))
                    
                    self.style.popScope()
                    self.svg_current_layer = current_layer
            elif cls == "Group":
                subgraphics = graphics.get("Graphics")
                if subgraphics is not None:
                    current_layer = self.svg_current_layer
                    self.style.appendScope()
                    g_emt = self.svg_dom.createElement("g")
                    g_emt.setAttribute("style",str(self.style))
                    current_layer.appendChild(g_emt)
                    self.svg_current_layer = g_emt
                    self.svgItterateGraffleGraphics(reversed(subgraphics))
                    self.style.popScope()
                    self.svg_current_layer = current_layer
            else:
                print "Don't know how to display Class \"%s\""%cls
                
                
            if graphics.get("Text") is not None:
                # have to write some text too ...
                coords = self.extractBoundCOordinates(graphics['Bounds'])
                self.svgSetGraffleFont(graphics.get("FontInfo"))
                
                x, y, width, height = coords
                x += float(graphics['Text'].get('Pad',0))
                y += float(graphics['Text'].get('VerticalPad',0))
                self.svg_addText(self.svg_current_layer, rtftext = graphics.get("Text").get("Text",""),
                                 x = x, y = y, width = width, height = height)
            self.style.popScope()
            
            
    def svgAddGraffleShapedGraphic(self, graphic):
        shape = graphic['Shape']
        
        extra_opts = {}
        if graphic.get("HFlip","NO")=="YES":
            extra_opts["HFlip"] = True
        if graphic.get("VFlip","NO")=="YES":
            extra_opts["VFlip"] = True
        if graphic.get("Rotation") is not None:
            extra_opts["Rotation"] = float(graphic["Rotation"])
            
        if shape in ("Rectangle", "RoundRect"):
            coords = self.extractBoundCOordinates(graphic['Bounds'])
            if graphic.get("ImageID") is not None:
                # TODO: images
                image_id = int(graphic["ImageID"])
                if len(self.imagelist) <= image_id:
                    print "Error - image out of range"
                    return
                image = self.imagelist[image_id]
                self.svg_addImage(self.svg_current_layer, bounds = coords, \
                                  href = image)
                print "Insert Image - " + str(image)
            else:
                # radius of corners is stored on the style in graffle
                sty = graphic.get("Style",{})
                stroke = sty.get("stroke",{})
                radius = stroke.get("CornerRadius",None)
                
                x, y   = coords[0], coords[1]
                width  = coords[2]# - coords[0]
                height = coords[3]# - coords[1]
                self.svg_addRect(self.svg_current_layer,
                                        x = x,
                                        y = y,
                                        width = width,
                                        height = height,
                                        rx=radius,
                                        ry=radius,
                                        **extra_opts)

        elif shape == "HorizontalTriangle":
            bounds = self.extractBoundCOordinates(graphic['Bounds'])
            self.svg_addHorizontalTriangle(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        elif shape == "RightTriangle":
            bounds = self.extractBoundCOordinates(graphic['Bounds'])
            self.svg_addRightTriangle(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        elif shape == "VerticalTriangle":
            bounds = self.extractBoundCOordinates(graphic['Bounds'])
            self.svg_addVerticalTriangle(self.svg_current_layer,
                                         bounds = bounds,
                                         **extra_opts \
                                         )
        elif shape == "Circle":
            # Actually can be an ellipse
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addEllipse(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        elif shape == "Bezier":
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addBezier(self.svg_current_layer,
                                        bounds, graphic["ShapeData"],
                                        **extra_opts)
        elif shape == "AdjustableArrow":
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addAdjustableArrow(self.svg_current_layer,
                                        bounds = bounds,
                                        graphic = graphic,
                                        **extra_opts \
                                        )
        elif shape == "Diamond":
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addDiamond(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        elif shape == "Subprocess":
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addSubprocess(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        elif shape == "Cloud":
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addCloud(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        else:
            print "Don't know how to display Shape %s"%str(graphic['Shape'])
            
    def extract_colour(self,col):
        # only gets rgb values (ignores a)
        return "".join( [mkHex(col["r"]), 
                        mkHex(col["g"]), 
                        mkHex(col["b"])] )
            
    def svgSetGraffleStyle(self, style):
        style_string = ""
        styles_list = []
        
        if style.get("fill") is not None:
            fill = style.get("fill")
            if fill.get("Draws","") == "NO":
                # don't display
                self.style["fill"]="none"
            else:
                grap_col = fill.get("Color")
                if grap_col is not None:
                    fill_col = self.extract_colour(grap_col)
                    self.style["fill"]="#%s"%fill_col
                
        if style.get("stroke") is not None:
            stroke = style.get("stroke")
            if stroke.get("Draws","") == "NO":
                self.style["stroke"]="none"
            else:
            
                grap_col = stroke.get("Color",{"r":0.,"g":0.,"b":0.})
                if grap_col is not None:
                    stroke_col = self.extract_colour(grap_col)
                    self.style["stroke"]="#%s"%stroke_col
            if stroke.get("HeadArrow") is not None:
                headarrow = stroke["HeadArrow"]
                if headarrow == "FilledArrow":
                    self.style["marker-end"]=":url(#Arrow1Lend)"
                    self.required_defs.add("Arrow1Lend")
                elif headarrow == "Bar":
                    #TODO
                    self.style["marker-end"]="url(#mBar)"
                    self.required_defs.add("Bar")                    
                elif headarrow == "0":
                    self.style["marker-end"] = "none"
                    
            if stroke.get("TailArrow") is not None:
                tailarrow = stroke["TailArrow"]
                if tailarrow == "FilledArrow":
                    self.style["marker-start"]="url(#Arrow1Lstart)"
                    self.required_defs.add("Arrow1Lstart")
                elif tailarrow == "CrowBall":
                    self.style["marker-start"]  = "url(#mCrowBall)"
                    self.required_defs.add("CrowBall")
                    
                elif tailarrow == "0":
                    self.style["marker-start"]="none"
            if stroke.get("Width") is not None:
                width = stroke["Width"]
                self.style["stroke-width"]="%fpx"%float(width)
            
            if stroke.get("Pattern") is not None:
                pattern = stroke["Pattern"]
                if pattern == 1:
                    self.style["stroke-dasharray"]="3 3"
                elif pattern == 2:
                    self.style["stroke-dasharray"]="5 5"
            
        if style.get("shadow",{}).get("Draws","NO") != "NO":
            # for some reason graffle has a shadow by default
            self.required_defs.add("DropShadow")
            self.style["filter"]="url(#DropShadow)"

    def svgSetGraffleFont(self, font):
        if font is None: return
        fontstuffs = []

        if font.get("Color") is not None:
            grap_col = font.get("Color")
            try:
                font_col = self.extract_colour(grap_col)
            except:
                font_col = "000000"
            fontstuffs.append("fill:#%s"%font_col)
            
        fontfam = font.get("Font")
        if fontfam is not None:
            if fontfam == "LucidaGrande":
                fontfam = "Luxi Sans"
            elif fontfam == "Courier":
                fontfam = "Courier New"
            elif fontfam == "GillSans":
                fontfam == "Arial Narrow"
            fontstuffs.append("font-family: %s"%fontfam)
            
        size = font.get("Size")
        if size is not None:
            fontstuffs.append("font-size:%dpx"%int(size) )
        
        self.svg_current_font = ";".join(fontstuffs)
        
        
    def svg_add_requirements(self):
        if "Arrow1Lend" in self.required_defs:
            # TODO
            p = xml.dom.minidom.parseString("""
            <defs><marker
               orient='auto'
               refY='0.0'
               refX='0.0'
               id='Arrow1Lend'
               style='overflow:visible;'>
              <path
                 id='path3666'
                 d='M -10,0.0 L -10.0,-2.0 L 0.0,0.0 L -10.0,2.0 z '
                 style='fill-rule:evenodd;stroke:#000000;stroke-width:1.0px;marker-start:none;' />
            </marker></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)
                
        if "Arrow1Lstart" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><marker
               orient='auto'
               refY='0.0'
               refX='0.0'
               id='Arrow1Lstart'
               style='overflow:visible'>
              <path
                 id='path3663'
                 d='M 10,0.0 L 10.0,-2.0 L 0.0,0.0 L 10.0,2.0 z'
                 style='fill-rule:evenodd;stroke:#000000;stroke-width:1.0px;marker-start:none'/>
            </marker></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)
                
        if "DropShadow" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><filter id='DropShadow' filterRes='100' x='0' y='0'>
               <feGaussianBlur stdDeviation='3' result='MyBlur'/>
               <feOffset in='MyBlur' dx='2' dy='4' result='movedBlur'/>
               <feMerge>
                   <feMergeNode in='movedBlur'/>
                   <feMergeNode in='SourceGraphic'/>
               </feMerge>
          </filter></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)
                
        if "CrowBall" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><marker
            refX='0'
            refY='0'
            orient='auto'
            id='mCrowBall'
            style='overflow:visible'>
            <path d='M 0.0,2.5 L 7.5,0.0 L 0.0,-2.5' 
             style='stroke:#000;stroke-width:1.0px;marker-start:none;fill:none;' />
            <circle cx='10' cy='0' r='2.5' style='stroke-width:1px; stroke: #000; fill:none;'/>
            </marker></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)
                
        if "Bar" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><marker
            refX='0'
            refY='0'
            orient='auto'
            id='mBar'
            style='overflow:visible'>
            <path d='M -7.5,-2.5 L -7.5,2.5' 
             style='stroke:#000;stroke-width:1.0px;marker-start:none;fill:none;' />
            </marker></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)

    def svg_addBezier(self, node, bounds, shapeopts, **opts):
        points = shapeopts["UnitPoints"]
        points =[self.extractBoundCOordinates(pt) for pt in points]
        c = [bounds[i] + (bounds[i+2]/2.) for i in [0, 1]] #centre
        rx = bounds[2]/2.
        ry = bounds[3]/2.

        # These points are relative to the bounds
        points = [ [c[0] + pt[0] * rx, c[1] + pt[1] * ry] for pt in points]

        if opts.get("HFlip", False):
            points = geom.h_flip_points(points)
        if opts.get("VFlip", False):
            points = geom.v_flip_points(points)
        if opts.get("Rotation") is not None:
            points = geom.rotate_points(points, opts["Rotation"])

        ptStrings = [",".join([str(b) for b in a]) for a in points]
        line_string = "M %s"%ptStrings[0] + " ".join(" L %s"%a for a in ptStrings[1:])
        
        path_tag = self.svg_dom.createElement("path")
        path_tag.setAttribute("id", opts.get("id",""))
        path_tag.setAttribute("style", str(self.style.scopeStyle()))
        path_tag.setAttribute("d", line_string)
        node.appendChild(path_tag)

    def svg_addEllipse(self, node, bounds, **opts):
        c = [bounds[i] + (bounds[i+2]/2.) for i in [0,1]] # centre of circle
        rx = bounds[2]/2.
        ry = bounds[3]/2.
        circle_tag = self.svg_dom.createElement("ellipse")
        circle_tag.setAttribute("id", opts.get("id",""))
        circle_tag.setAttribute("style", str(self.style.scopeStyle()))
        circle_tag.setAttribute("cx", str(c[0]))
        circle_tag.setAttribute("cy", str(c[1]))
        circle_tag.setAttribute("rx", str(rx))
        circle_tag.setAttribute("ry", str(ry))
        node.appendChild(circle_tag)

    def svg_addAdjustableArrow(self, node, bounds, graphic,**opts):
        x,y,width,height = [float(a) for a in bounds]
        ratio = float(graphic["ShapeData"]["ratio"])
        neck = float(graphic["ShapeData"]["width"])
        neck_delta = height*(1-ratio)/2
        self.svg_addPath(node,[[x,y+neck_delta], [x+width-neck,y+neck_delta], [x+width-neck,y],
                          [x+width,y+height/2], [x+width-neck,y+height], [x+width-neck,y+height-neck_delta],
                          [x,y+height-neck_delta]],closepath=True,**opts)

    def svg_addDiamond(self, node, bounds, **opts):
        x, y, width, height = [float(a) for a in bounds]
        self.svg_addPath(node,[[x + (width / 2), y],
                               [x + width, y + (height / 2)],
                               [x + (width / 2), y + height],
                               [x, y + (height / 2)]],
                                closepath=True, **opts)
                                
    def svg_addSubprocess(self, node, bounds, **opts):
        # TODO: check ISO flowchart specification for correct ratio?
        x, y, width, height = [float(a) for a in bounds]
        self.svg_addRect(node, width=width, height=height, x=x, y=y, **opts)
        # add the vertical lines
        
        x_offset = width * 0.1
        self.svg_addPath(node, [[x + x_offset, y],
                                [x + x_offset, y + height]],
                                closepath=False, **opts)
                                
        self.svg_addPath(node, [[x + width - x_offset, y],
                                [x + width - x_offset, y + height]],
                                closepath=False, **opts)
                                
    def svg_addCloud(self, node, bounds, **opts):
        x, y, width, height = [float(a) for a in bounds]
        #TODO: draw an actual shape (should abstract the shape scaling etc)
        self.svg_addRect(node, x=x, y=y, width=width, height=height, **opts)
                             
    def svg_addPath(self, node, pts, **opts):
        # do geometry mapping here
        mypts = pts
        if opts.get("HFlip",False):
            mypts = geom.h_flip_points(mypts)
        if opts.get("VFlip",False):            
            mypts = geom.v_flip_points(mypts)
        if opts.get("Rotation") is not None:
            mypts = geom.rotate_points(mypts,opts["Rotation"])
            
        ptStrings = [",".join([str(b) for b in a]) for a in mypts]
        line_string = "M %s"%ptStrings[0] + " ".join(" L %s"%a for a in ptStrings[1:] )
        if opts.get("closepath",False) == True:
            line_string = line_string + " z"
        path_tag = self.svg_dom.createElement("path")
        path_tag.setAttribute("id", opts.get("id",""))
        path_tag.setAttribute("style", str(self.style.scopeStyle()))
        path_tag.setAttribute("d", line_string)
        node.appendChild(path_tag)
        
    def svg_addHorizontalTriangle(self, node, bounds, rotation = 0, **opts):
        """Graffle has the "HorizontalTriangle" Shape"""
        x,y,width,height = [float(a) for a in bounds]
        self.svg_addPath(node, [[x,y],[x+width,y+height/2], [x,y+height]], \
                        closepath=True, **opts)
                        
    def svg_addImage(self, node, bounds, **opts):
        """SVG viewers should support images - unfortunately many don't :-("""
        x,y,width,height = [float(a) for a in bounds]
        image_tag = self.svg_dom.createElement("image")
        image_tag.setAttribute("x", str(x))
        image_tag.setAttribute("y", str(y))
        image_tag.setAttribute("width", str(width))
        image_tag.setAttribute("height", str(height))
        image_tag.setAttribute("xlink:href", str(opts.get("href","")))
        image_tag.setAttribute("style", str(self.style.scopeStyle()))
        node.appendChild(image_tag)
        
    def svg_addRightTriangle(self, node, bounds, rotation = 0, **opts):
        """Graffle has the "RightTriangle" Shape"""
        x,y,width,height = [float(a) for a in bounds]
        self.svg_addPath(node, [[x,y],[x+width,y+height], [x,y+height]], \
                        closepath=True, **opts)

    def svg_addVerticalTriangle(self, node, bounds, rotation = 0, **opts):
        """Graffle has the "RightTriangle" Shape"""
        x,y,width,height = [float(a) for a in bounds]
        self.svg_addPath(node, [[x,y],[x+width,y], [x+width/2,y+height]], \
                        closepath=True, **opts)
            
    def svg_addRect(self, node, **opts):
        """Add an svg rect"""
        if opts is None:
            opts = {}
        rect_tag = self.svg_dom.createElement("rect")
        rect_tag.setAttribute("id",opts.get("id",""))
        rect_tag.setAttribute("width",str(opts["width"]))
        rect_tag.setAttribute("height",str(opts["height"]))
        rect_tag.setAttribute("x",str(opts.get("x","0")))
        rect_tag.setAttribute("y",str(opts.get("y","0")))
        if opts.get("rx") is not None:
            rect_tag.setAttribute("rx",str(opts["rx"]))
            rect_tag.setAttribute("ry",str(opts["ry"]))
            
        rect_tag.setAttribute("style", str(self.style.scopeStyle()))
        node.appendChild(rect_tag)
        
        
    def svg_addText(self,node,**opts):
        """Add an svg text element"""
        text_tag = self.svg_dom.createElement("text")
        text_tag.setAttribute("id",opts.get("id",""))
        text_tag.setAttribute("x",str(opts.get("x","0")))
        text_tag.setAttribute("y",str(opts.get("y","0")))
        text_tag.setAttribute("style", ";".join( \
                                [str(self.style.scopeStyle()),self.svg_current_font]))
        node.appendChild(text_tag)
        
        # TODO: lines need to be moved down by the correct size
        
        # Generator
        lines = extractRTFString(opts["rtftext"])
        
        i = 0
        for span in lines:
            self.svg_addLine(text_tag,text = span["string"], style = span["style"],\
                    y_offset = i, line_height = 12, **opts)
            i+=1
        
    def svg_addLine(self,textnode, **opts):
        """Add a line of text"""
        tspan_node = self.svg_dom.createElement("tspan")
        tspan_node.setAttribute("id",opts.get("id",""))
        tspan_node.setAttribute("x",str(opts.get("x","0")))
        y_pos = float(opts.get("y",0)) + \
                opts.get("line_height",12) * (opts.get("y_offset",0)+1)
        if opts.get("style") is not None:
            tspan_node.setAttribute("style",str(opts["style"]))
        tspan_node.setAttribute("y",str(y_pos))
        actual_string = self.svg_dom.createTextNode(opts.get("text"," "))
        tspan_node.appendChild(actual_string)
        textnode.appendChild(tspan_node)
Exemple #8
0
def extractRTFString(s):
    """Extract a string and some styling info"""
    bracket_depth = 0
    instruction = False
    inst_code = ""

    ftable = FontTable()
    colortable = ColorTable()
    # The string being generated:
    std_string = ""
    style = CascadingStyles()
    # Want to set these as defaults even if not specified
    style.appendScope()
    """TODO: 
     extract styling 
     
     e.g. 
     {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf460
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural

\f0\fs28 \cf0 Next ads are represented in a book-ended carousel on end screen}
    """
    def do_instruction(inst_code, i):
        if inst_code == "b":
            style["font-weight"] = "bold"
        if inst_code == "ql":
            style["text-align"] = "left"
        elif inst_code == "qr":
            style["text-align"] = "right"
        elif inst_code == "qj":
            style["text-align"] = "justify"
        elif inst_code == "qc":
            style["text-align"] = "center"
        elif inst_code == "fonttbl":
            i = ftable.parseTable(s, i)
        elif inst_code == "colortbl":
            i = colortable.parseTable(s, i)
        elif inst_code[0] == "f" and inst_code[1:].isdigit():
            # Font looked up in font table
            font = ftable.fonts.get(int(inst_code[1:]), {})
            for k, v in font.items():
                if k != "":
                    style[k] = v
        elif inst_code[:2] == "fs" and isint(inst_code[2:]):
            # font size - RTF specifies half pt sizes
            style["font-size"] = "%.1fpx" % (float(inst_code[2:]) / 2.0)
        elif inst_code[:2] == "cf" and isint(inst_code[2:]):
            # font colour is enytry int(inst_code[2:]) in the colour table
            style["fill"] = "#" + colortable[int(inst_code[2:])]
            style["stroke"] = "#" + colortable[int(inst_code[2:])]
        return i

    i = -1
    result_lines = []
    while i < len(s) - 1:
        i += 1
        c = s[i]
        if c == "{":
            bracket_depth += 1
            style.appendScope()
        elif c == "}":
            if std_string != "":
                yield {"string": std_string, "style": str(style)}
                std_string = ""
            style.popScope()
            bracket_depth -= 1
        elif c == "\\":
            if len(inst_code) > 0:
                i = do_instruction(inst_code, i)
            instruction = True
            inst_code = ""

        if instruction:
            if c == " ":
                instruction = False
                i = do_instruction(inst_code, i)
            elif c == "\n":
                instruction = False
                if inst_code == "":
                    # new line so yield
                    yield {"string": std_string, "style": str(style)}
                    std_string = ""
                else:
                    i = do_instruction(inst_code, i)
            elif not c in "\\;":
                inst_code += c

        else:
            if bracket_depth == 1:
                if not c in "{}\\\n\r":
                    # those characters are escaped
                    std_string += c
    style.popScope()
Exemple #9
0
class GraffleParser(object):
    g_dom = None
    svg_dom = None
    svg_current_layer = None
    svg_current_font = ""
    svg_def = None

    def __init__(self):
        self.svg_dom = xml.dom.minidom.Document()
        self.svg_dom.doctype = ""
        svg_tag = self.svg_dom.createElement("svg")
        svg_tag.setAttribute("xmlns", "http://www.w3.org/2000/svg")
        svg_tag.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink")
        self.svg_dom.appendChild(svg_tag)
        def_tag = self.svg_dom.createElement("defs")
        svg_tag.appendChild(def_tag)
        self.svg_def = def_tag

        # set of required macros
        self.required_defs = set()

        self.style = CascadingStyles()
        self.style.appendScope()
        self.style["fill"] = "#fff"
        self.style["stroke"] = "#000000"

        graphic_tag = self.svg_dom.createElement("g")
        graphic_tag.setAttribute("style", str(self.style))
        svg_tag.appendChild(graphic_tag)
        self.svg_current_layer = graphic_tag

    @property
    def svg(self):
        """Return the svg document"""
        return self.svg_dom.toprettyxml()

    def walkGraffle(self, xmlstr, **kwargs):
        """Walk over the file"""
        self.g_dom = xml.dom.minidom.parseString(xmlstr)

        self.walkGraffleDoc(self.g_dom, **kwargs)
        self.svg_add_requirements()

    def walkGraffleDoc(self, parent, page=0):
        # want to pass this around like a continuation
        cont = nodeListGen(parent.childNodes)
        i = 0
        mydict = None
        for e in cont:
            if e.nodeType == e.DOCUMENT_TYPE_NODE:
                pass
            localname = e.localName

            if localname == "plist":
                # Apple's main container
                self.walkGraffleDoc(e, page)

            if localname == "dict":
                mydict = self.ReturnGraffleDict(e)

        if mydict is not None:
            # Extract file information
            self.fileinfo = fileinfo.FileInfo(mydict)
            # Graffle lists it's image references separately
            self.imagelist = mydict.get("ImageList", [])
            # Sometimes have multiple sheets
            if mydict.get("Sheets") is not None:
                self.extractPage(mydict["Sheets"][page])
            else:
                self.extractPage(mydict)

    def extractPage(self, grafflenodeasdict):
        mydict = grafflenodeasdict

        if self.fileinfo.fmt_version >= 6:
            # Graffle version 6 has a background graphic
            background = mydict["BackgroundGraphic"]
            # draw this
            self.svgItterateGraffleGraphics([background])
        elif self.fileinfo.fmt_version < 6:
            # Version 5 has a CanvasColor property instead
            colour = mydict.get("CanvasColor")
            if colour is not None:
                sty = {}

                # We have to guess the document's dimensions from the print size
                # - these numbers appear to match up with the background size in
                #  version 6.
                origin = parseCoords(mydict.get("CanvasOrigin", "{0,0}"))
                print_info = self.fileinfo.printinfo

                paper_size = print_info.paper_size

                Lmargin = print_info.left_margin
                Rmargin = print_info.right_margin
                Tmargin = print_info.top_margin
                Bmargin = print_info.bottom_margin

                x, y = origin
                width = paper_size[0] - Lmargin - Rmargin
                height = paper_size[1] - Bmargin - Tmargin
                self.svg_addRect(self.svg_current_layer,
                                 x=x,
                                 y=y,
                                 width=width,
                                 height=height,
                                 rx=None,
                                 ry=None)

        graphics = mydict["GraphicsList"]
        self.svgItterateGraffleGraphics(graphics)

    def ReturnGraffleNode(self, parent):
        """Return a python representation of the 
           node passed"""
        if parent.nodeType == parent.TEXT_NODE:
            return parent.wholeText
        elif parent.localName == "dict":
            return self.ReturnGraffleDict(parent)
        elif parent.localName == "array":
            return self.ReturnGraffleArray(parent)
        elif parent.localName == "true":
            return True
        elif parent.localName == "false":
            return False
        elif parent.localName in ["string", "real", "integer"]:
            return self.ReturnGraffleNode(parent.firstChild)
        return parent.nodeType

    def ReturnGraffleDict(self, parent):
        """Graffle has dicts like
            <dict>
                <key />
                <integer />
                <key />
                <string />
            </dict>
            - pass the <dict> node to this method
        """
        retdict = {}
        cont = nodeListGen(parent.childNodes)
        key, val = None, None
        for e in cont:
            if e.nodeType == e.TEXT_NODE:
                continue
            localname = e.localName
            if localname == "key":
                key = self.ReturnGraffleNode(e.firstChild)
            else:
                val = self.ReturnGraffleNode(e)
                retdict[key] = val
        return retdict

    def ReturnGraffleArray(self, parent):
        """Graffle has arrays like
            <array>
                <string />
                <string />
            </array>
            - pass the <array> node to this method
        """
        retlist = []
        cont = nodeListGen(parent.childNodes)
        for e in cont:
            if e.nodeType == e.TEXT_NODE:
                continue
            retlist.append(self.ReturnGraffleNode(e))
        return retlist

    def extractMagnetCoordinates(self, mgnts):
        pts = [parseCoords(a) for a in mgnts]
        return pts

    def extractBoundCOordinates(self, bnds):
        bnds = bnds[1:-1].strip()
        bnds = bnds.split(",")
        coords = []
        for bnd in bnds:
            bnd = bnd.replace("{", "")
            bnd = bnd.replace("}", "")
            coords.append(float(bnd))

        return coords

    def svgItterateGraffleGraphics(self, GraphicsList):
        """parent should be a list of """
        for graphics in GraphicsList:
            # Styling
            self.style.appendScope()
            if graphics.get("Style") is not None:
                self.svgSetGraffleStyle(graphics.get("Style"))

            cls = graphics["Class"]
            if cls == "SolidGraphic":
                # used as background - add a
                shallowcopy = {"Shape": "Rectangle"}
                shallowcopy.update(graphics)
                self.svgAddGraffleShapedGraphic(shallowcopy)

            elif cls == "ShapedGraphic":
                try:
                    self.svgAddGraffleShapedGraphic(graphics)
                except:
                    raise
                    print "could not show shaped graphic"

            elif cls == "LineGraphic":
                pts = self.extractMagnetCoordinates(graphics["Points"])
                self.style["fill"] = "none"
                if graphics.get("OrthogonalBarAutomatic") == False:
                    bar_pos = graphics.get("OrthogonalBarPosition")
                    if bar_pos is not None:
                        # Decide where to place the orthogonal position

                        bar_pos = float(bar_pos)
                        """
                        # This isn't right
                        out_pts = []
                        i = 0
                        while i < len(pts) - 1:
                            p1 = pts[i]
                            p2 = pts[i+1]
                            newpt = [p1[0] + bar_pos, p1[1]]
                            out_pts.append(p1)
                            out_pts.append(newpt)
                            out_pts.append(p2)
                            i+=2
                        pts = out_pts
                        """

                self.svg_addPath(self.svg_current_layer, pts)

            elif cls == "TableGroup":
                # In Progress
                table_graphics = graphics.get("Graphics")
                if table_graphics is not None:
                    current_layer = self.svg_current_layer
                    self.style.appendScope()
                    g_emt = self.svg_dom.createElement("g")
                    g_emt.setAttribute("style", str(self.style))
                    current_layer.appendChild(g_emt)
                    self.svg_current_layer = g_emt

                    self.svgItterateGraffleGraphics(reversed(table_graphics))

                    self.style.popScope()
                    self.svg_current_layer = current_layer
            elif cls == "Group":
                subgraphics = graphics.get("Graphics")
                if subgraphics is not None:
                    current_layer = self.svg_current_layer
                    self.style.appendScope()
                    g_emt = self.svg_dom.createElement("g")
                    g_emt.setAttribute("style", str(self.style))
                    current_layer.appendChild(g_emt)
                    self.svg_current_layer = g_emt
                    self.svgItterateGraffleGraphics(reversed(subgraphics))
                    self.style.popScope()
                    self.svg_current_layer = current_layer
            else:
                print "Don't know how to display Class \"%s\"" % cls

            if graphics.get("Text") is not None:
                # have to write some text too ...
                coords = self.extractBoundCOordinates(graphics['Bounds'])
                self.svgSetGraffleFont(graphics.get("FontInfo"))

                x, y, width, height = coords
                x += float(graphics['Text'].get('Pad', 0))
                y += float(graphics['Text'].get('VerticalPad', 0))
                self.svg_addText(self.svg_current_layer,
                                 rtftext=graphics.get("Text").get("Text", ""),
                                 x=x,
                                 y=y,
                                 width=width,
                                 height=height)
            self.style.popScope()

    def svgAddGraffleShapedGraphic(self, graphic):
        shape = graphic['Shape']

        extra_opts = {}
        if graphic.get("HFlip", "NO") == "YES":
            extra_opts["HFlip"] = True
        if graphic.get("VFlip", "NO") == "YES":
            extra_opts["VFlip"] = True
        if graphic.get("Rotation") is not None:
            extra_opts["Rotation"] = float(graphic["Rotation"])

        if shape in ("Rectangle", "RoundRect"):
            coords = self.extractBoundCOordinates(graphic['Bounds'])
            if graphic.get("ImageID") is not None:
                # TODO: images
                image_id = int(graphic["ImageID"])
                if len(self.imagelist) <= image_id:
                    print "Error - image out of range"
                    return
                image = self.imagelist[image_id]
                self.svg_addImage(self.svg_current_layer, bounds = coords, \
                                  href = image)
                print "Insert Image - " + str(image)
            else:
                # radius of corners is stored on the style in graffle
                sty = graphic.get("Style", {})
                stroke = sty.get("stroke", {})
                radius = stroke.get("CornerRadius", None)

                x, y = coords[0], coords[1]
                width = coords[2]  # - coords[0]
                height = coords[3]  # - coords[1]
                self.svg_addRect(self.svg_current_layer,
                                 x=x,
                                 y=y,
                                 width=width,
                                 height=height,
                                 rx=radius,
                                 ry=radius,
                                 **extra_opts)

        elif shape == "HorizontalTriangle":
            bounds = self.extractBoundCOordinates(graphic['Bounds'])
            self.svg_addHorizontalTriangle(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        elif shape == "RightTriangle":
            bounds = self.extractBoundCOordinates(graphic['Bounds'])
            self.svg_addRightTriangle(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        elif shape == "VerticalTriangle":
            bounds = self.extractBoundCOordinates(graphic['Bounds'])
            self.svg_addVerticalTriangle(self.svg_current_layer,
                                         bounds = bounds,
                                         **extra_opts \
                                         )
        elif shape == "Circle":
            # Actually can be an ellipse
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addEllipse(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        elif shape == "Bezier":
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addBezier(self.svg_current_layer, bounds,
                               graphic["ShapeData"], **extra_opts)
        elif shape == "AdjustableArrow":
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addAdjustableArrow(self.svg_current_layer,
                                        bounds = bounds,
                                        graphic = graphic,
                                        **extra_opts \
                                        )
        elif shape == "Diamond":
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addDiamond(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        elif shape == "Subprocess":
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addSubprocess(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        elif shape == "Cloud":
            bounds = self.extractBoundCOordinates(graphic["Bounds"])
            self.svg_addCloud(self.svg_current_layer,
                                        bounds = bounds,
                                        **extra_opts \
                                        )
        else:
            print "Don't know how to display Shape %s" % str(graphic['Shape'])

    def extract_colour(self, col):
        # only gets rgb values (ignores a)
        return "".join([mkHex(col["r"]), mkHex(col["g"]), mkHex(col["b"])])

    def svgSetGraffleStyle(self, style):
        style_string = ""
        styles_list = []

        if style.get("fill") is not None:
            fill = style.get("fill")
            if fill.get("Draws", "") == "NO":
                # don't display
                self.style["fill"] = "none"
            else:
                grap_col = fill.get("Color")
                if grap_col is not None:
                    fill_col = self.extract_colour(grap_col)
                    self.style["fill"] = "#%s" % fill_col

        if style.get("stroke") is not None:
            stroke = style.get("stroke")
            if stroke.get("Draws", "") == "NO":
                self.style["stroke"] = "none"
            else:

                grap_col = stroke.get("Color", {"r": 0., "g": 0., "b": 0.})
                if grap_col is not None:
                    stroke_col = self.extract_colour(grap_col)
                    self.style["stroke"] = "#%s" % stroke_col
            if stroke.get("HeadArrow") is not None:
                headarrow = stroke["HeadArrow"]
                if headarrow == "FilledArrow":
                    self.style["marker-end"] = ":url(#Arrow1Lend)"
                    self.required_defs.add("Arrow1Lend")
                elif headarrow == "Bar":
                    #TODO
                    self.style["marker-end"] = "url(#mBar)"
                    self.required_defs.add("Bar")
                elif headarrow == "0":
                    self.style["marker-end"] = "none"

            if stroke.get("TailArrow") is not None:
                tailarrow = stroke["TailArrow"]
                if tailarrow == "FilledArrow":
                    self.style["marker-start"] = "url(#Arrow1Lstart)"
                    self.required_defs.add("Arrow1Lstart")
                elif tailarrow == "CrowBall":
                    self.style["marker-start"] = "url(#mCrowBall)"
                    self.required_defs.add("CrowBall")

                elif tailarrow == "0":
                    self.style["marker-start"] = "none"
            if stroke.get("Width") is not None:
                width = stroke["Width"]
                self.style["stroke-width"] = "%fpx" % float(width)

            if stroke.get("Pattern") is not None:
                pattern = stroke["Pattern"]
                if pattern == 1:
                    self.style["stroke-dasharray"] = "3 3"
                elif pattern == 2:
                    self.style["stroke-dasharray"] = "5 5"

        if style.get("shadow", {}).get("Draws", "NO") != "NO":
            # for some reason graffle has a shadow by default
            self.required_defs.add("DropShadow")
            self.style["filter"] = "url(#DropShadow)"

    def svgSetGraffleFont(self, font):
        if font is None: return
        fontstuffs = []

        if font.get("Color") is not None:
            grap_col = font.get("Color")
            try:
                font_col = self.extract_colour(grap_col)
            except:
                font_col = "000000"
            fontstuffs.append("fill:#%s" % font_col)

        fontfam = font.get("Font")
        if fontfam is not None:
            if fontfam == "LucidaGrande":
                fontfam = "Luxi Sans"
            elif fontfam == "Courier":
                fontfam = "Courier New"
            elif fontfam == "GillSans":
                fontfam == "Arial Narrow"
            fontstuffs.append("font-family: %s" % fontfam)

        size = font.get("Size")
        if size is not None:
            fontstuffs.append("font-size:%dpx" % int(size))

        self.svg_current_font = ";".join(fontstuffs)

    def svg_add_requirements(self):
        if "Arrow1Lend" in self.required_defs:
            # TODO
            p = xml.dom.minidom.parseString("""
            <defs><marker
               orient='auto'
               refY='0.0'
               refX='0.0'
               id='Arrow1Lend'
               style='overflow:visible;'>
              <path
                 id='path3666'
                 d='M -10,0.0 L -10.0,-2.0 L 0.0,0.0 L -10.0,2.0 z '
                 style='fill-rule:evenodd;stroke:#000000;stroke-width:1.0px;marker-start:none;' />
            </marker></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)

        if "Arrow1Lstart" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><marker
               orient='auto'
               refY='0.0'
               refX='0.0'
               id='Arrow1Lstart'
               style='overflow:visible'>
              <path
                 id='path3663'
                 d='M 10,0.0 L 10.0,-2.0 L 0.0,0.0 L 10.0,2.0 z'
                 style='fill-rule:evenodd;stroke:#000000;stroke-width:1.0px;marker-start:none'/>
            </marker></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)

        if "DropShadow" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><filter id='DropShadow' filterRes='100' x='0' y='0'>
               <feGaussianBlur stdDeviation='3' result='MyBlur'/>
               <feOffset in='MyBlur' dx='2' dy='4' result='movedBlur'/>
               <feMerge>
                   <feMergeNode in='movedBlur'/>
                   <feMergeNode in='SourceGraphic'/>
               </feMerge>
          </filter></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)

        if "CrowBall" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><marker
            refX='0'
            refY='0'
            orient='auto'
            id='mCrowBall'
            style='overflow:visible'>
            <path d='M 0.0,2.5 L 7.5,0.0 L 0.0,-2.5' 
             style='stroke:#000;stroke-width:1.0px;marker-start:none;fill:none;' />
            <circle cx='10' cy='0' r='2.5' style='stroke-width:1px; stroke: #000; fill:none;'/>
            </marker></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)

        if "Bar" in self.required_defs:
            p = xml.dom.minidom.parseString("""
            <defs><marker
            refX='0'
            refY='0'
            orient='auto'
            id='mBar'
            style='overflow:visible'>
            <path d='M -7.5,-2.5 L -7.5,2.5' 
             style='stroke:#000;stroke-width:1.0px;marker-start:none;fill:none;' />
            </marker></defs>""")
            def_node = p.childNodes[0]
            for node in def_node.childNodes:
                self.svg_def.appendChild(node)

    def svg_addBezier(self, node, bounds, shapeopts, **opts):
        points = shapeopts["UnitPoints"]
        points = [self.extractBoundCOordinates(pt) for pt in points]
        c = [bounds[i] + (bounds[i + 2] / 2.) for i in [0, 1]]  #centre
        rx = bounds[2] / 2.
        ry = bounds[3] / 2.

        # These points are relative to the bounds
        points = [[c[0] + pt[0] * rx, c[1] + pt[1] * ry] for pt in points]

        if opts.get("HFlip", False):
            points = geom.h_flip_points(points)
        if opts.get("VFlip", False):
            points = geom.v_flip_points(points)
        if opts.get("Rotation") is not None:
            points = geom.rotate_points(points, opts["Rotation"])

        ptStrings = [",".join([str(b) for b in a]) for a in points]
        line_string = "M %s" % ptStrings[0] + " ".join(" L %s" % a
                                                       for a in ptStrings[1:])

        path_tag = self.svg_dom.createElement("path")
        path_tag.setAttribute("id", opts.get("id", ""))
        path_tag.setAttribute("style", str(self.style.scopeStyle()))
        path_tag.setAttribute("d", line_string)
        node.appendChild(path_tag)

    def svg_addEllipse(self, node, bounds, **opts):
        c = [bounds[i] + (bounds[i + 2] / 2.)
             for i in [0, 1]]  # centre of circle
        rx = bounds[2] / 2.
        ry = bounds[3] / 2.
        circle_tag = self.svg_dom.createElement("ellipse")
        circle_tag.setAttribute("id", opts.get("id", ""))
        circle_tag.setAttribute("style", str(self.style.scopeStyle()))
        circle_tag.setAttribute("cx", str(c[0]))
        circle_tag.setAttribute("cy", str(c[1]))
        circle_tag.setAttribute("rx", str(rx))
        circle_tag.setAttribute("ry", str(ry))
        node.appendChild(circle_tag)

    def svg_addAdjustableArrow(self, node, bounds, graphic, **opts):
        x, y, width, height = [float(a) for a in bounds]
        ratio = float(graphic["ShapeData"]["ratio"])
        neck = float(graphic["ShapeData"]["width"])
        neck_delta = height * (1 - ratio) / 2
        self.svg_addPath(
            node, [[x, y + neck_delta], [x + width - neck, y + neck_delta],
                   [x + width - neck, y], [x + width, y + height / 2],
                   [x + width - neck, y + height],
                   [x + width - neck, y + height - neck_delta],
                   [x, y + height - neck_delta]],
            closepath=True,
            **opts)

    def svg_addDiamond(self, node, bounds, **opts):
        x, y, width, height = [float(a) for a in bounds]
        self.svg_addPath(
            node, [[x + (width / 2), y], [x + width, y + (height / 2)],
                   [x + (width / 2), y + height], [x, y + (height / 2)]],
            closepath=True,
            **opts)

    def svg_addSubprocess(self, node, bounds, **opts):
        # TODO: check ISO flowchart specification for correct ratio?
        x, y, width, height = [float(a) for a in bounds]
        self.svg_addRect(node, width=width, height=height, x=x, y=y, **opts)
        # add the vertical lines

        x_offset = width * 0.1
        self.svg_addPath(node, [[x + x_offset, y], [x + x_offset, y + height]],
                         closepath=False,
                         **opts)

        self.svg_addPath(
            node,
            [[x + width - x_offset, y], [x + width - x_offset, y + height]],
            closepath=False,
            **opts)

    def svg_addCloud(self, node, bounds, **opts):
        x, y, width, height = [float(a) for a in bounds]
        #TODO: draw an actual shape (should abstract the shape scaling etc)
        self.svg_addRect(node, x=x, y=y, width=width, height=height, **opts)

    def svg_addPath(self, node, pts, **opts):
        # do geometry mapping here
        mypts = pts
        if opts.get("HFlip", False):
            mypts = geom.h_flip_points(mypts)
        if opts.get("VFlip", False):
            mypts = geom.v_flip_points(mypts)
        if opts.get("Rotation") is not None:
            mypts = geom.rotate_points(mypts, opts["Rotation"])

        ptStrings = [",".join([str(b) for b in a]) for a in mypts]
        line_string = "M %s" % ptStrings[0] + " ".join(" L %s" % a
                                                       for a in ptStrings[1:])
        if opts.get("closepath", False) == True:
            line_string = line_string + " z"
        path_tag = self.svg_dom.createElement("path")
        path_tag.setAttribute("id", opts.get("id", ""))
        path_tag.setAttribute("style", str(self.style.scopeStyle()))
        path_tag.setAttribute("d", line_string)
        node.appendChild(path_tag)

    def svg_addHorizontalTriangle(self, node, bounds, rotation=0, **opts):
        """Graffle has the "HorizontalTriangle" Shape"""
        x, y, width, height = [float(a) for a in bounds]
        self.svg_addPath(node, [[x,y],[x+width,y+height/2], [x,y+height]], \
                        closepath=True, **opts)

    def svg_addImage(self, node, bounds, **opts):
        """SVG viewers should support images - unfortunately many don't :-("""
        x, y, width, height = [float(a) for a in bounds]
        image_tag = self.svg_dom.createElement("image")
        image_tag.setAttribute("x", str(x))
        image_tag.setAttribute("y", str(y))
        image_tag.setAttribute("width", str(width))
        image_tag.setAttribute("height", str(height))
        image_tag.setAttribute("xlink:href", str(opts.get("href", "")))
        image_tag.setAttribute("style", str(self.style.scopeStyle()))
        node.appendChild(image_tag)

    def svg_addRightTriangle(self, node, bounds, rotation=0, **opts):
        """Graffle has the "RightTriangle" Shape"""
        x, y, width, height = [float(a) for a in bounds]
        self.svg_addPath(node, [[x,y],[x+width,y+height], [x,y+height]], \
                        closepath=True, **opts)

    def svg_addVerticalTriangle(self, node, bounds, rotation=0, **opts):
        """Graffle has the "RightTriangle" Shape"""
        x, y, width, height = [float(a) for a in bounds]
        self.svg_addPath(node, [[x,y],[x+width,y], [x+width/2,y+height]], \
                        closepath=True, **opts)

    def svg_addRect(self, node, **opts):
        """Add an svg rect"""
        if opts is None:
            opts = {}
        rect_tag = self.svg_dom.createElement("rect")
        rect_tag.setAttribute("id", opts.get("id", ""))
        rect_tag.setAttribute("width", str(opts["width"]))
        rect_tag.setAttribute("height", str(opts["height"]))
        rect_tag.setAttribute("x", str(opts.get("x", "0")))
        rect_tag.setAttribute("y", str(opts.get("y", "0")))
        if opts.get("rx") is not None:
            rect_tag.setAttribute("rx", str(opts["rx"]))
            rect_tag.setAttribute("ry", str(opts["ry"]))

        rect_tag.setAttribute("style", str(self.style.scopeStyle()))
        node.appendChild(rect_tag)

    def svg_addText(self, node, **opts):
        """Add an svg text element"""
        text_tag = self.svg_dom.createElement("text")
        text_tag.setAttribute("id", opts.get("id", ""))
        text_tag.setAttribute("x", str(opts.get("x", "0")))
        text_tag.setAttribute("y", str(opts.get("y", "0")))
        text_tag.setAttribute("style", ";".join( \
                                [str(self.style.scopeStyle()),self.svg_current_font]))
        node.appendChild(text_tag)

        # TODO: lines need to be moved down by the correct size

        # Generator
        lines = extractRTFString(opts["rtftext"])

        i = 0
        for span in lines:
            self.svg_addLine(text_tag,text = span["string"], style = span["style"],\
                    y_offset = i, line_height = 12, **opts)
            i += 1

    def svg_addLine(self, textnode, **opts):
        """Add a line of text"""
        tspan_node = self.svg_dom.createElement("tspan")
        tspan_node.setAttribute("id", opts.get("id", ""))
        tspan_node.setAttribute("x", str(opts.get("x", "0")))
        y_pos = float(opts.get("y",0)) + \
                opts.get("line_height",12) * (opts.get("y_offset",0)+1)
        if opts.get("style") is not None:
            tspan_node.setAttribute("style", str(opts["style"]))
        tspan_node.setAttribute("y", str(y_pos))
        actual_string = self.svg_dom.createTextNode(opts.get("text", " "))
        tspan_node.appendChild(actual_string)
        textnode.appendChild(tspan_node)