
#***************************************************************************
#*                                                                         *
#*   Copyright (c) 2009 Yorik van Havre <yorik@uncreated.net>              * 
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU Lesser General Public License (LGPL)    *
#*   as published by the Free Software Foundation; either version 2 of     *
#*   the License, or (at your option) any later version.                   *
#*   for detail see the LICENCE text file.                                 *
#*                                                                         *
#*   This program is distributed in the hope that it will be useful,       *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU Library General Public License for more details.                  *
#*                                                                         *
#*   You should have received a copy of the GNU Library General Public     *
#*   License along with this program; if not, write to the Free Software   *
#*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
#*   USA                                                                   *
#*                                                                         *
#***************************************************************************

__title__="FreeCAD Draft Workbench - SVG importer/exporter"
__author__ = "Yorik van Havre, Sebastian Hoogen"
__url__ = ["http://www.freecadweb.org"]

'''
This script imports SVG files in FreeCAD. Currently only reads the following entities:
paths, lines, circular arcs ,rects, circles, ellipses, polygons, polylines.
currently unsupported: use, image
'''
#ToDo:
# ignoring CDATA
# handle image element (external references and inline base64)
# debug Problem with 'Sans' font from Inkscape
# debug Problem with fill color
# implement inherting fill style from group
# handle relative units

import xml.sax, string, FreeCAD, os, math, re, Draft, DraftVecUtils
from FreeCAD import Vector

try: import FreeCADGui
except ImportError: gui = False
else: gui = True
try: draftui = FreeCADGui.draftToolBar
except AttributeError: draftui = None

if open.__module__ == '__builtin__':
  pythonopen = open

svgcolors = {
          'Pink': (255, 192, 203),
          'Blue': (0, 0, 255),
          'Honeydew': (240, 255, 240),
          'Purple': (128, 0, 128),
          'Fuchsia': (255, 0, 255),
          'LawnGreen': (124, 252, 0),
          'Amethyst': (153, 102, 204),
          'Crimson': (220, 20, 60),
          'White': (255, 255, 255),
          'NavajoWhite': (255, 222, 173),
          'Cornsilk': (255, 248, 220),
          'Bisque': (255, 228, 196),
          'PaleGreen': (152, 251, 152),
          'Brown': (165, 42, 42),
          'DarkTurquoise': (0, 206, 209),
          'DarkGreen': (0, 100, 0),
          'MediumOrchid': (186, 85, 211),
          'Chocolate': (210, 105, 30),
          'PapayaWhip': (255, 239, 213),
          'Olive': (128, 128, 0),
          'Silver': (192, 192, 192),
          'PeachPuff': (255, 218, 185),
          'Plum': (221, 160, 221),
          'DarkGoldenrod': (184, 134, 11),
          'SlateGrey': (112, 128, 144),
          'MintCream': (245, 255, 250),
          'CornflowerBlue': (100, 149, 237),
          'Gold': (255, 215, 0),
          'HotPink': (255, 105, 180),
          'DarkBlue': (0, 0, 139),
          'LimeGreen': (50, 205, 50),
          'DeepSkyBlue': (0, 191, 255),
          'DarkKhaki': (189, 183, 107),
          'LightGrey': (211, 211, 211),
          'Yellow': (255, 255, 0),
          'Gainsboro': (220, 220, 220),
          'MistyRose': (255, 228, 225),
          'SandyBrown': (244, 164, 96),
          'DeepPink': (255, 20, 147),
          'Magenta': (255, 0, 255),
          'AliceBlue': (240, 248, 255),
          'DarkCyan': (0, 139, 139),
          'DarkSlateGrey': (47, 79, 79),
          'GreenYellow': (173, 255, 47),
          'DarkOrchid': (153, 50, 204),
          'OliveDrab': (107, 142, 35),
          'Chartreuse': (127, 255, 0),
          'Peru': (205, 133, 63),
          'Orange': (255, 165, 0),
          'Red': (255, 0, 0),
          'Wheat': (245, 222, 179),
          'LightCyan': (224, 255, 255),
          'LightSeaGreen': (32, 178, 170),
          'BlueViolet': (138, 43, 226),
          'LightSlateGrey': (119, 136, 153),
          'Cyan': (0, 255, 255),
          'MediumPurple': (147, 112, 219),
          'MidnightBlue': (25, 25, 112),
          'FireBrick': (178, 34, 34),
          'PaleTurquoise': (175, 238, 238),
          'PaleGoldenrod': (238, 232, 170),
          'Gray': (128, 128, 128),
          'MediumSeaGreen': (60, 179, 113),
          'Moccasin': (255, 228, 181),
          'Ivory': (255, 255, 240),
          'DarkSlateBlue': (72, 61, 139),
          'Beige': (245, 245, 220),
          'Green': (0, 128, 0),
          'SlateBlue': (106, 90, 205),
          'Teal': (0, 128, 128),
          'Azure': (240, 255, 255),
          'LightSteelBlue': (176, 196, 222),
          'DimGrey': (105, 105, 105),
          'Tan': (210, 180, 140),
          'AntiqueWhite': (250, 235, 215),
          'SkyBlue': (135, 206, 235),
          'GhostWhite': (248, 248, 255),
          'MediumTurquoise': (72, 209, 204),
          'FloralWhite': (255, 250, 240),
          'LavenderBlush': (255, 240, 245),
          'SeaGreen': (46, 139, 87),
          'Lavender': (230, 230, 250),
          'BlanchedAlmond': (255, 235, 205),
          'DarkOliveGreen': (85, 107, 47),
          'DarkSeaGreen': (143, 188, 143),
          'SpringGreen': (0, 255, 127),
          'Navy': (0, 0, 128),
          'Orchid': (218, 112, 214),
          'SaddleBrown': (139, 69, 19),
          'IndianRed': (205, 92, 92),
          'Snow': (255, 250, 250),
          'SteelBlue': (70, 130, 180),
          'MediumSlateBlue': (123, 104, 238),
          'Black': (0, 0, 0),
          'LightBlue': (173, 216, 230),
          'Turquoise': (64, 224, 208),
          'MediumVioletRed': (199, 21, 133),
          'DarkViolet': (148, 0, 211),
          'DarkGray': (169, 169, 169),
          'Salmon': (250, 128, 114),
          'DarkMagenta': (139, 0, 139),
          'Tomato': (255, 99, 71),
          'WhiteSmoke': (245, 245, 245),
          'Goldenrod': (218, 165, 32),
          'MediumSpringGreen': (0, 250, 154),
          'DodgerBlue': (30, 144, 255),
          'Aqua': (0, 255, 255),
          'ForestGreen': (34, 139, 34),
          'LemonChiffon': (255, 250, 205),
          'LightSlateGray': (119, 136, 153),
          'SlateGray': (112, 128, 144),
          'LightGray': (211, 211, 211),
          'Indigo': (75, 0, 130),
          'CadetBlue': (95, 158, 160),
          'LightYellow': (255, 255, 224),
          'DarkOrange': (255, 140, 0),
          'PowderBlue': (176, 224, 230),
          'RoyalBlue': (65, 105, 225),
          'Sienna': (160, 82, 45),
          'Thistle': (216, 191, 216),
          'Lime': (0, 255, 0),
          'Seashell': (255, 245, 238),
          'DarkRed': (139, 0, 0),
          'LightSkyBlue': (135, 206, 250),
          'YellowGreen': (154, 205, 50),
          'Aquamarine': (127, 255, 212),
          'LightCoral': (240, 128, 128),
          'DarkSlateGray': (47, 79, 79),
          'Khaki': (240, 230, 140),
          'DarkGrey': (169, 169, 169),
          'BurlyWood': (222, 184, 135),
          'LightGoldenrodYellow': (250, 250, 210),
          'MediumBlue': (0, 0, 205),
          'DarkSalmon': (233, 150, 122),
          'RosyBrown': (188, 143, 143),
          'LightSalmon': (255, 160, 122),
          'PaleVioletRed': (219, 112, 147),
          'Coral': (255, 127, 80),
          'Violet': (238, 130, 238),
          'Grey': (128, 128, 128),
          'LightGreen': (144, 238, 144),
          'Linen': (250, 240, 230),
          'OrangeRed': (255, 69, 0),
          'DimGray': (105, 105, 105),
          'Maroon': (128, 0, 0),
          'LightPink': (255, 182, 193),
          'MediumAquamarine': (102, 205, 170),
          'OldLace': (253, 245, 230)
          }
svgcolorslower = dict((key.lower(),value) for key,value in \
    list(svgcolors.items()))

def getcolor(color):
    "checks if the given string is a RGB value, or if it is a named color. returns 1-based RGBA tuple."
    if (color[0] == "#"):
        if len(color) == 7:
            r = float(int(color[1:3],16)/255.0)
            g = float(int(color[3:5],16)/255.0)
            b = float(int(color[5:],16)/255.0)
        elif len(color) == 4: #expand the hex digits
            r = float(int(color[1],16)*17/255.0)
            g = float(int(color[2],16)*17/255.0)
            b = float(int(color[3],16)*17/255.0)
        return (r,g,b,0.0)
    elif color.lower().startswith('rgb('):
        cvalues=color[3:].lstrip('(').rstrip(')').replace('%','').split(',')
        if '%' in color:
            r,g,b = [int(cv)/100.0 for cv in cvalues]
        else:
            r,g,b = [int(cv)/255.0 for cv in cvalues]
        return (r,g,b,0.0)
    else:
        v=svgcolorslower.get(color.lower())
        if v:
            r,g,b = [float(vf)/255.0 for vf in v]
            return (r,g,b,0.0)
        #for k,v in svgcolors.iteritems():
        #    if (k.lower() == color.lower()): pass

def transformCopyShape(shape,m):
    """apply transformation matrix m on given shape
since OCCT 6.8.0 transformShape can be used to apply certian non-orthogonal
transformations on shapes. This way a conversion to BSplines in
transformGeometry can be avoided."""
    if abs(m.A11**2+m.A12**2 -m.A21**2-m.A22**2) < 1e-8 and \
            abs(m.A11*m.A21+m.A12*m.A22) < 1e-8: #no shear
        try:
            newshape=shape.copy()
            newshape.transformShape(m)
            return newshape
        except Part.OCCError: # older versions of OCCT will refuse to work on
            pass              # non-orthogonal matrices
    return shape.transformGeometry(m)

def getsize(length,mode='discard',base=1):
        """parses length values containing number and unit
        with mode 'discard': extracts a number from the given string (removes unit suffixes)
        with mode 'tuple': return number and unit as a tuple
        with mode 'css': convert the unit to px assuming 90dpi
        with mode 'mm': convert the unit to millimeter assuming 90dpi"""
        tomm={
                '' : 25.4/90, #default
                'px' : 25.4/90,
                'pt' : 1.25*25.4/90,
                'pc' : 15*25.4/90,
                'mm' : 1.0,
                'cm' : 10.0,
                'in' : 25.4,
                'em':  15*2.54/90, #arbitrarily chosen; has to depend on font size
                'ex': 10*2.54/90, #arbitrarily chosen; has to depend on font size

                '%': 100 #arbitrarily chosen; has to depend on vieport size or (for filling patterns) on bounding box
                }
        topx={
                '' : 1.0, #default
                'px' : 1.0,
                'pt' : 1.25,
                'pc' : 15,
                'mm' : 90.0/25.4,
                'cm' : 90.0/254.0,
                'in' : 90,
                'em':  15, #arbitrarily chosen; has to depend on font size
                'ex':  10, #arbitrarily chosen; has to depend on font size

                '%': 100 #arbitrarily chosen; has to depend on vieport size or (for filling patterns) on bounding box
                }
        number, exponent, unit=re.findall('([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)(px|pt|pc|mm|cm|in|em|ex|%)?',length)[0]
        if mode =='discard':
                return float(number)
        elif mode == 'tuple':
                return float(number),unit
        elif mode == 'isabsolute':
                return unit in ('mm','cm','in','px','pt')
        elif mode == 'mm':
                return float(number)*tomm[unit]
        elif mode == 'css':
                if unit != '%':
                        return float(number)*topx[unit]
                else:
                        return float(number)*base

def makewire(path,checkclosed=False,donttry=False):
        '''try to make a wire out of the list of edges. If the 'Wire' functions fails or the wire is not
        closed if required the 'connectEdgesToWires' function is used'''
        if not donttry:
                try:
                        import Part
                        sh = Part.Wire(Part.__sortEdges__(path))
                        #sh = Part.Wire(path)
                        isok = (not checkclosed) or sh.isClosed()
                except Part.OCCError:# BRep_API:command not done
                        isok = False
        if donttry or not isok:
                        #Code from wmayer forum p15549 to fix the tolerance problem
                        #original tolerance = 0.00001
                        comp=Part.Compound(path)
                        sh = comp.connectEdgesToWires(False,10**(-1*(Draft.precision()-2))).Wires[0]
        return sh

def arccenter2end(center,rx,ry,angle1,angledelta,xrotation=0.0):
        '''calculate start and end vector and flags of an arc given in center parametrization
        see http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
        returns (v1,v2,largerc,sweep)'''
        vr1=Vector(rx*math.cos(angle1),ry*math.sin(angle1),0)
        vr2=Vector(rx*math.cos(angle1+angledelta),ry*math.sin(angle1+angledelta),0)
        mxrot=FreeCAD.Matrix()
        mxrot.rotateZ(xrotation)
        v1 = mxrot.multiply(vr1).add(center)
        v2 = mxrot.multiply(vr2).add(center)
        fa = ((abs(angledelta) / math.pi) % 2) > 1 # <180deg
        fs = angledelta < 0
        return v1,v2,fa,fs

def arcend2center(lastvec,currentvec,rx,ry,xrotation=0.0,correction=False):
        '''calculate (positive and negative) possible centers for an arc in endpoint parameterization
        see http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
        rotation or x-axis has to be specified in radians (CCW)
        the sweepflag is interpreted as: sweepflag <==>  arc is travelled clockwise 
        returns [(vcenter+,angle1+,angledelta+),(...-)]'''
        #scalefacsign = 1 if (largeflag != sweepflag) else -1
        rx = float(rx)
        ry = float(ry)
        v0 = lastvec.sub(currentvec)
        v0.multiply(0.5)
        m1=FreeCAD.Matrix()
        m1.rotateZ(-xrotation) #Formular 6.5.1
        v1=m1.multiply(v0)
        if correction:
                eparam = v1.x**2 / rx**2 + v1.y**2 / ry**2
                if eparam > 1:
                        eproot = math.sqrt(eparam)
                        rx = eproot * rx
                        ry = eproot * ry
        denom = rx**2 * v1.y**2+ ry**2 * v1.x**2
        numer = rx**2 * ry**2 -denom
        results=[]
        if abs(numer/denom) < 10**(-1*(Draft.precision())):
                scalefacpos = 0
        else:
                try:
                        scalefacpos = math.sqrt(numer/denom)
                except ValueError:
                        FreeCAD.Console.PrintMessage('sqrt(%f/%f)\n' % (numer,denom))
                        scalefacpos = 0
        for scalefacsign in (1,-1):
            scalefac = scalefacpos * scalefacsign
            vcx1 = Vector(v1.y*rx/ry,-v1.x*ry/rx,0).multiply(scalefac)  # Step2 F.6.5.2
            m2=FreeCAD.Matrix()
            m2.rotateZ(xrotation)
            centeroff = currentvec.add(lastvec)
            centeroff.multiply(.5)
            vcenter = m2.multiply(vcx1).add(centeroff) # Step3 F.6.5.3
            #angle1 = Vector(1,0,0).getAngle(Vector((v1.x-vcx1.x)/rx,(v1.y-vcx1.y)/ry,0)) # F.6.5.5
            #angledelta = Vector((v1.x-vcx1.x)/rx,(v1.y-vcx1.y)/ry,0).getAngle(Vector((-v1.x-vcx1.x)/rx,(-v1.y-vcx1.y)/ry,0)) # F.6.5.6
            #we need the right sign for the angle 
            angle1 = DraftVecUtils.angle(Vector(1,0,0),Vector((v1.x-vcx1.x)/rx,(v1.y-vcx1.y)/ry,0)) # F.6.5.5
            angledelta = DraftVecUtils.angle(Vector((v1.x-vcx1.x)/rx,(v1.y-vcx1.y)/ry,0),Vector((-v1.x-vcx1.x)/rx,(-v1.y-vcx1.y)/ry,0)) # F.6.5.6
            results.append((vcenter,angle1,angledelta))
        return results,(rx,ry)


def getrgb(color):
        "returns a rgb value #000000 from a freecad color"
        r = str(hex(int(color[0]*255)))[2:].zfill(2)
        g = str(hex(int(color[1]*255)))[2:].zfill(2)
        b = str(hex(int(color[2]*255)))[2:].zfill(2)
        return "#"+r+g+b

class svgHandler(xml.sax.ContentHandler):
        "this handler parses the svg files and creates freecad objects"

        def __init__(self):
                "retrieving Draft parameters"
                params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
                self.style = params.GetInt("svgstyle")
                self.disableUnitScaling = params.GetBool("svgDisableUnitScaling",False)
                self.count = 0
                self.transform = None
                self.grouptransform = []
                self.lastdim = None
                self.viewbox = None
                self.symbols = {}
                self.currentsymbol = None

                global Part
                import Part
        
                if gui and draftui:
                        r = float(draftui.color.red()/255.0)
                        g = float(draftui.color.green()/255.0)
                        b = float(draftui.color.blue()/255.0)
                        self.lw = float(draftui.linewidth)
                else:
                        self.lw = float(params.GetInt("linewidth"))
                        c = params.GetUnsigned("color")
                        r = float(((c>>24)&0xFF)/255)
                        g = float(((c>>16)&0xFF)/255)
                        b = float(((c>>8)&0xFF)/255)
                self.col = (r,g,b,0.0)

        def format(self,obj):
                "applies styles to passed object"
                if self.style and gui:
                        v = obj.ViewObject
                        if self.color: v.LineColor = self.color
                        if self.width: v.LineWidth = self.width
                        if self.fill: v.ShapeColor = self.fill
        
        def startElement(self, name, attrs):

                # reorganizing data into a nice clean dictionary

                self.count += 1

                FreeCAD.Console.PrintMessage('processing element %d: %s\n'%(self.count,name))
                FreeCAD.Console.PrintMessage('existing group transform: %s\n'%(str(self.grouptransform)))
                
                data = {}
                for (keyword,content) in list(attrs.items()):
                        #print keyword,content
                        if keyword != "style":
                            content = content.replace(',',' ')
                            content = content.split()
                        #print keyword,content
                        data[keyword]=content

                if 'style' in data:
                        if not data['style']:
                                pass#empty style attribute stops inhertig from parent
                        else:
                                content = data['style'][0].replace(' ','')
                                content = content.split(';')
                                for i in content:
                                        pair = i.split(':')
                                        if len(pair)>1: data[pair[0]]=pair[1]

                for k in ['x','y','x1','y1','x2','y2','r','rx','ry','cx','cy','width','height']:
                        if k in data:
                                data[k] = getsize(data[k][0],'css')

                for k in ['fill','stroke','stroke-width','font-size']:
                        if k in data:
                                if isinstance(data[k],list):
                                    if data[k][0].lower().startswith("rgb("):
                                        data[k] = ",".join(data[k])
                                    else:
                                        data[k]=data[k][0]

                # extracting style info
                        
                self.fill = None
                self.color = None
                self.width = None
                self.text = None
                
                if name == 'svg':
                        m=FreeCAD.Matrix()
                        if not self.disableUnitScaling:
                            if 'width' in data and 'height' in data and \
                                'viewBox' in data:
                                    vbw=float(data['viewBox'][2])
                                    vbh=float(data['viewBox'][3])
                                    w=attrs.getValue('width')
                                    h=attrs.getValue('height')
                                    self.viewbox=(vbw,vbh)
                                    if len(self.grouptransform)==0:
                                        unitmode='mm'
                                    else: #nested svg element
                                        unitmode='css'
                                    abw = getsize(w,unitmode)
                                    abh = getsize(h,unitmode)
                                    sx=abw/vbw
                                    sy=abh/vbh
                                    preservearstr=' '.join(data.get('preserveAspectRatio',[])).lower()
                                    uniformscaling = round(sx/sy,5) == 1
                                    if uniformscaling:
                                        m.scale(Vector(sx,sy,1))
                                    else:
                                        FreeCAD.Console.PrintWarning('Scaling Factors do not match!!!\n')
                                        if preservearstr.startswith('none'):
                                            m.scale(Vector(sx,sy,1))
                                        else: #preserve the aspect ratio
                                            if preservearstr.endswith('slice'):
                                                sxy=max(sx,sy)
                                            else:
                                                sxy=min(sx,sy)
                                            m.scale(Vector(sxy,sxy,1))
                            elif len(self.grouptransform)==0:
                                #fallback to 90 dpi
                                m.scale(Vector(25.4/90.0,25.4/90.0,1))
                        self.grouptransform.append(m) 
                if 'fill' in data:
                        if data['fill'][0] != 'none':
                                self.fill = getcolor(data['fill'])
                if 'stroke' in data:
                        if data['stroke'][0] != 'none':
                                self.color = getcolor(data['stroke'])
                if 'stroke-width' in data:
                        if data['stroke-width'] != 'none':
                                self.width = getsize(data['stroke-width'],'css')
                if 'transform' in data:
                        m = self.getMatrix(attrs.getValue('transform'))
                        if name == "g":
                                self.grouptransform.append(m)
                        else:
                                self.transform = m
                else:
                        if name == "g":
                                self.grouptransform.append(FreeCAD.Matrix())

                if (self.style == 1):
                        self.color = self.col
                        self.width = self.lw

                pathname = None
                if 'id' in data:
                        pathname = data['id'][0]
                        FreeCAD.Console.PrintMessage('name: %s\n'%pathname)
                        
                # processing paths
                        
                if name == "path":
                        FreeCAD.Console.PrintMessage('data: %s\n'%str(data))
                        
                        if not pathname: pathname = 'Path'

                        path = []
                        point = []
                        lastvec = Vector(0,0,0)
                        lastpole = None
                        command = None
                        relative = False
                        firstvec = None

                        if "freecad:basepoint1" in data:
                                p1 = data["freecad:basepoint1"]
                                p1 = Vector(float(p1[0]),-float(p1[1]),0)
                                p2 = data["freecad:basepoint2"]
                                p2 = Vector(float(p2[0]),-float(p2[1]),0)
                                p3 = data["freecad:dimpoint"]
                                p3 = Vector(float(p3[0]),-float(p3[1]),0)
                                obj = Draft.makeDimension(p1,p2,p3)
                                self.applyTrans(obj)
                                self.format(obj)
                                self.lastdim = obj
                                data['d']=[]
                        pathcommandsre=re.compile('\s*?([mMlLhHvVaAcCqQsStTzZ])\s*?([^mMlLhHvVaAcCqQsStTzZ]*)\s*?',re.DOTALL)
                        pointsre=re.compile('([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)',re.DOTALL)
                        for d,pointsstr in pathcommandsre.findall(' '.join(data['d'])):
                                relative = d.islower()
                                pointlist = [float(number) for number,exponent in pointsre.findall(pointsstr.replace(',',' '))]

                                if (d == "M" or d == "m"):
                                        x = pointlist.pop(0)
                                        y = pointlist.pop(0)
                                        if path:
                                                #sh = Part.Wire(path)
                                                sh = makewire(path)
                                                if self.fill and sh.isClosed():
                                                    sh = Part.Face(sh)
                                                sh = self.applyTrans(sh)
                                                obj = self.doc.addObject("Part::Feature",pathname)
                                                obj.Shape = sh
                                                self.format(obj)
                                                if self.currentsymbol:
                                                    self.symbols[self.currentsymbol].append(obj)
                                                path = []
                                                #if firstvec:
                                                #        lastvec = firstvec #Move relative to last move command not last draw command
                                        if relative:
                                                lastvec = lastvec.add(Vector(x,-y,0))
                                        else:
                                                lastvec = Vector(x,-y,0)
                                        firstvec = lastvec
                                        FreeCAD.Console.PrintMessage('move %s\n'%str(lastvec))
                                        lastpole = None
                                if (d == "L" or d == "l") or \
                                        ((d == 'm' or d == 'M') and pointlist) :
                                        for x,y in zip(pointlist[0::2],pointlist[1::2]):
                                                if relative:
                                                        currentvec = lastvec.add(Vector(x,-y,0))
                                                else:
                                                        currentvec = Vector(x,-y,0)
                                                if not DraftVecUtils.equals(lastvec,currentvec):
                                                        seg = Part.Line(lastvec,currentvec).toShape()
                                                        FreeCAD.Console.PrintMessage("line %s %s\n" %(lastvec,currentvec))
                                                        lastvec = currentvec
                                                        path.append(seg)
                                                lastpole = None
                                elif (d == "H" or d == "h"):
                                        for x in pointlist:
                                                if relative:
                                                        currentvec = lastvec.add(Vector(x,0,0))
                                                else:
                                                        currentvec = Vector(x,lastvec.y,0)
                                                seg = Part.Line(lastvec,currentvec).toShape()
                                                lastvec = currentvec
                                                lastpole = None
                                                path.append(seg)
                                elif (d == "V" or d == "v"):
                                        for y in pointlist:
                                                if relative:
                                                        currentvec = lastvec.add(Vector(0,-y,0))
                                                else:
                                                        currentvec = Vector(lastvec.x,-y,0)
                                                seg = Part.Line(lastvec,currentvec).toShape()
                                                lastvec = currentvec
                                                lastpole = None
                                                path.append(seg)
                                elif (d == "A" or d == "a"):
                                        for rx,ry,xrotation, largeflag, sweepflag,x,y in \
                                                zip(pointlist[0::7],pointlist[1::7],pointlist[2::7],pointlist[3::7],pointlist[4::7],pointlist[5::7],pointlist[6::7]):

                                                #support for large-arc and x-rotation are missing
                                                if relative:
                                                        currentvec = lastvec.add(Vector(x,-y,0))
                                                else:
                                                        currentvec = Vector(x,-y,0)
                                                chord = currentvec.sub(lastvec)
                                                if (not largeflag) and abs(rx-ry) < 10**(-1*Draft.precision()): # small circular arc
                                                        # perp = chord.cross(Vector(0,0,-1))
                                                        # here is a better way to find the perpendicular
                                                        if sweepflag == 1:
                                                                # clockwise
                                                                perp = DraftVecUtils.rotate2D(chord,-math.pi/2)
                                                        else:
                                                                # anticlockwise
                                                                perp = DraftVecUtils.rotate2D(chord,math.pi/2)
                                                        chord.multiply(.5)
                                                        if chord.Length > rx: a = 0
                                                        else: a = math.sqrt(rx**2-chord.Length**2)
                                                        s = rx - a
                                                        perp.multiply(s/perp.Length)
                                                        midpoint = lastvec.add(chord.add(perp))
                                                        seg = Part.Arc(lastvec,midpoint,currentvec).toShape()
                                                else:# big arc or elliptical arc
                                                        solution,(rx,ry) = arcend2center(lastvec,currentvec,rx,ry,math.radians(-xrotation),True) 
                                                        negsol = (largeflag != sweepflag)
                                                        vcenter,angle1,angledelta = solution[negsol]
                                                        #print angle1
                                                        #print angledelta
                                                        if ry > rx:
                                                                rx,ry=ry,rx
                                                                swapaxis = True
                                                        else:
                                                                swapaxis = False
                                                         #print 'Elliptical arc %s rx=%f ry=%f' % (vcenter,rx,ry)
                                                        e1 = Part.Ellipse(vcenter,rx,ry)
                                                        if sweepflag:
                                                                #angledelta=-(-angledelta % (math.pi *2)) # Step4
                                                                #angledelta=(-angledelta % (math.pi *2)) # Step4
                                                                angle1  = angle1+angledelta
                                                                angledelta = -angledelta
                                                                #angle1 = math.pi - angle1 

                                                        e1a = Part.Arc(e1,angle1-swapaxis*math.radians(90),\
                                                                angle1+angledelta-swapaxis*math.radians(90))
                                                        #e1a = Part.Arc(e1,angle1-0*swapaxis*math.radians(90),angle1+angledelta-0*swapaxis*math.radians(90))
                                                        if swapaxis or xrotation >  10**(-1*Draft.precision()):
                                                                m3=FreeCAD.Matrix()
                                                                m3.move(vcenter)
                                                                rot90=FreeCAD.Matrix(0,-1,0,0,1,0) #90
                                                                #swapaxism=FreeCAD.Matrix(0,1,0,0,1,0) 
                                                                if swapaxis:
                                                                        m3=m3.multiply(rot90)
                                                                m3.rotateZ(math.radians(-xrotation))
                                                                m3.move(vcenter.multiply(-1))
                                                                e1a.transform(m3)
                                                        seg = e1a.toShape()
                                                        if sweepflag:
                                                                seg.reverse()
                                                                #obj = self.doc.addObject("Part::Feature",'DEBUG %s'%pathname) #DEBUG
                                                                #obj.Shape = seg #DEBUG
                                                                #seg = Part.Line(lastvec,currentvec).toShape() #DEBUG
                                                lastvec = currentvec
                                                lastpole = None
                                                path.append(seg)
                                elif (d == "C" or d == "c") or\
                                        (d =="S" or d == "s"):
                                        smooth = (d == 'S'  or d == 's')
                                        if smooth:
                                            piter = list(zip(pointlist[2::4],pointlist[3::4],pointlist[0::4],pointlist[1::4],pointlist[2::4],pointlist[3::4]))
                                        else:
                                            piter = list(zip(pointlist[0::6],pointlist[1::6],pointlist[2::6],pointlist[3::6],pointlist[4::6],pointlist[5::6]))
                                        for p1x,p1y,p2x,p2y,x,y in piter:
                                                if smooth:
                                                        if lastpole is not None and lastpole[0]=='cubic':
                                                                pole1 = lastvec.sub(lastpole[1]).add(lastvec)
                                                        else:
                                                                pole1 = lastvec
                                                else:
                                                        if relative:
                                                                pole1 = lastvec.add(Vector(p1x,-p1y,0))
                                                        else:
                                                                pole1 = Vector(p1x,-p1y,0)
                                                if relative:
                                                        currentvec = lastvec.add(Vector(x,-y,0))
                                                        pole2 = lastvec.add(Vector(p2x,-p2y,0))
                                                else:
                                                        currentvec = Vector(x,-y,0)
                                                        pole2 = Vector(p2x,-p2y,0)

                                                if not DraftVecUtils.equals(currentvec,lastvec):
                                                        mainv = currentvec.sub(lastvec)
                                                        pole1v = lastvec.add(pole1)
                                                        pole2v = currentvec.add(pole2)
                                                        #print "cubic curve data:",mainv.normalize(),pole1v.normalize(),pole2v.normalize()
                                                        if True and \
                                                        pole1.distanceToLine(lastvec,currentvec) < 10**(-1*(2+Draft.precision())) and \
                                                        pole2.distanceToLine(lastvec,currentvec) < 10**(-1*(2+Draft.precision())):
                                                                #print "straight segment"
                                                                seg = Part.Line(lastvec,currentvec).toShape()
                                                        else:
                                                                #print "cubic bezier segment"
                                                                b = Part.BezierCurve()
                                                                b.setPoles([lastvec,pole1,pole2,currentvec])
                                                                seg = b.toShape()
                                                        #print "connect ",lastvec,currentvec
                                                        lastvec = currentvec
                                                        lastpole = ('cubic',pole2)
                                                        path.append(seg)
                                elif (d == "Q" or d == "q") or\
                                        (d =="T" or d == "t"):
                                        smooth = (d == 'T'  or d == 't')
                                        if smooth:
                                            piter = list(zip(pointlist[1::2],pointlist[1::2],pointlist[0::2],pointlist[1::2]))
                                        else:
                                            piter = list(zip(pointlist[0::4],pointlist[1::4],pointlist[2::4],pointlist[3::4]))
                                        for px,py,x,y in piter:
                                                if smooth:
                                                        if lastpole is not None and lastpole[0]=='quadratic':
                                                                pole = lastvec.sub(lastpole[1]).add(lastvec)
                                                        else:
                                                                pole = lastvec
                                                else:
                                                        if relative:
                                                                pole = lastvec.add(Vector(px,-py,0))
                                                        else:
                                                                pole = Vector(px,-py,0)
                                                if relative:
                                                        currentvec = lastvec.add(Vector(x,-y,0))
                                                else:
                                                        currentvec = Vector(x,-y,0)

                                                if not DraftVecUtils.equals(currentvec,lastvec):
                                                        if True and \
                                                        pole.distanceToLine(lastvec,currentvec) < 20**(-1*(2+Draft.precision())):
                                                                #print "straight segment"
                                                                seg = Part.Line(lastvec,currentvec).toShape()
                                                        else:
                                                                #print "quadratic bezier segment"
                                                                b = Part.BezierCurve()
                                                                b.setPoles([lastvec,pole,currentvec])
                                                                seg = b.toShape()
                                                        #print "connect ",lastvec,currentvec
                                                        lastvec = currentvec
                                                        lastpole = ('quadratic',pole)
                                                        path.append(seg)
                                elif (d == "Z") or (d == "z"):
                                        if not DraftVecUtils.equals(lastvec,firstvec):
                                            try:
                                                seg = Part.Line(lastvec,firstvec).toShape()
                                            except Part.OCCError:
                                                pass
                                            else:
                                                path.append(seg)
                                        if path: #the path should be closed by now
                                                #sh=makewire(path,True)
                                                sh=makewire(path,donttry=False)
                                                if self.fill: sh = Part.Face(sh)
                                                sh = self.applyTrans(sh)
                                                obj = self.doc.addObject("Part::Feature",pathname)
                                                obj.Shape = sh
                                                self.format(obj)
                                                path = []
                                                if firstvec:
                                                        lastvec = firstvec #Move relative to recent draw command
                                                point = []
                                                command = None
                                                if self.currentsymbol:
                                                    self.symbols[self.currentsymbol].append(obj)
                        if path:
                                sh=makewire(path,checkclosed=False)
                                #sh = Part.Wire(path)
                                if self.fill and sh.isClosed():
                                    sh = Part.Face(sh)
                                sh = self.applyTrans(sh)
                                obj = self.doc.addObject("Part::Feature",pathname)
                                obj.Shape = sh
                                self.format(obj)
                                if self.currentsymbol:
                                    self.symbols[self.currentsymbol].append(obj)


                # processing rects

                if name == "rect":
                        if not pathname: pathname = 'Rectangle'
                        edges = []
                        if not "x" in data:
                            data["x"] = 0
                        if not "y" in data:
                            data["y"] = 0
                        if ('rx' not in data or data['rx'] < 10**(-1*Draft.precision())) and \
                           ('ry' not in data or data['ry'] < 10**(-1*Draft.precision())): #negative values are invalid
#                        if True: 
                                p1 = Vector(data['x'],-data['y'],0)
                                p2 = Vector(data['x']+data['width'],-data['y'],0)
                                p3 = Vector(data['x']+data['width'],-data['y']-data['height'],0)
                                p4 = Vector(data['x'],-data['y']-data['height'],0)
                                edges.append(Part.Line(p1,p2).toShape())
                                edges.append(Part.Line(p2,p3).toShape())
                                edges.append(Part.Line(p3,p4).toShape())
                                edges.append(Part.Line(p4,p1).toShape())
                        else: #rounded edges
                                #ToTo: check for ry>rx !!!!
                                rx = data.get('rx')
                                ry = data.get('ry') or rx
                                rx = rx or ry 
                                if rx > 2 * data['width']:
                                        rx = data['width'] / 2.0
                                if ry > 2 * data['height']:
                                       ry = data['height'] / 2.0
                                if rx > ry:
                                    mj = rx
                                    mi = ry
                                else:
                                    mj = ry
                                    mi = rx
                                p1=Vector(data['x']+rx,-data['y']-data['height']+ry,0)
                                e1=Part.Ellipse(p1,mj,mi)
                                p2=Vector(data['x']+data['width']-rx,-data['y']-data['height']+ry,0)
                                e2=Part.Ellipse(p2,mj,mi)
                                p3=Vector(data['x']+data['width']-rx,-data['y']-ry,0)
                                e3=Part.Ellipse(p3,mj,mi)
                                p4=Vector(data['x']+rx,-data['y']-ry,0)
                                e4=Part.Ellipse(p4,mj,mi)
                                if rx > ry:
                                        e1a=Part.Arc(e1,math.radians(180),math.radians(270))
                                        e2a=Part.Arc(e2,math.radians(270),math.radians(360))
                                        e3a=Part.Arc(e3,math.radians(0),math.radians(90))
                                        e4a=Part.Arc(e4,math.radians(90),math.radians(180))
                                        esh=[e1a.toShape(),e2a.toShape(),e3a.toShape(),e4a.toShape()]
                                else:
                                        e1a=Part.Arc(e1,math.radians(90),math.radians(180))
                                        e2a=Part.Arc(e2,math.radians(180),math.radians(270))
                                        e3a=Part.Arc(e3,math.radians(270),math.radians(360))
                                        e4a=Part.Arc(e4,math.radians(0),math.radians(90))
                                        rot90=FreeCAD.Matrix(0,-1,0,0,1,0)
                                        esh=[]
                                        for arc,point in ((e1a,p1),(e2a,p2),(e3a,p3),(e4a,p4)):
                                                m1=FreeCAD.Matrix()
                                                m1.move(point.multiply(1))
                                                m1=m1.multiply(rot90)
                                                m1.move(point.multiply(-1))
                                                #m1.move(point)
                                                arc.transform(m1)
                                                esh.append(arc.toShape())
                                for esh1,esh2 in zip(esh[-1:]+esh[:-1],esh):
                                        p1,p2 = esh1.Vertexes[-1].Point,esh2.Vertexes[0].Point
                                        if not DraftVecUtils.equals(p1,p2):
                                            edges.append(Part.Line(esh1.Vertexes[-1].Point,esh2.Vertexes[0].Point).toShape()) #straight segments
                                        edges.append(esh2) # elliptical segments
                        sh = Part.Wire(edges)
                        if self.fill: sh = Part.Face(sh)
                        sh = self.applyTrans(sh)
                        obj = self.doc.addObject("Part::Feature",pathname)
                        obj.Shape = sh
                        self.format(obj)
                        if self.currentsymbol:
                            self.symbols[self.currentsymbol].append(obj)
                        
                # processing lines

                if name == "line":
                        if not pathname: pathname = 'Line'
                        p1 = Vector(data['x1'],-data['y1'],0)
                        p2 = Vector(data['x2'],-data['y2'],0)
                        sh = Part.Line(p1,p2).toShape()
                        sh = self.applyTrans(sh)
                        obj = self.doc.addObject("Part::Feature",pathname)
                        obj.Shape = sh
                        self.format(obj)
                        if self.currentsymbol:
                            self.symbols[self.currentsymbol].append(obj)

                # processing polylines and polygons

                if name == "polyline" or name == "polygon":
                        '''a simpler implementation would be sh = Part.makePolygon([Vector(svgx,-svgy,0) for svgx,svgy in zip(points[0::2],points[1::2])])
                        but there would be more difficlult to search for duplicate points beforehand.'''
                        if not pathname: pathname = 'Polyline'
                        points=[float(d) for d in data['points']]
                        FreeCAD.Console.PrintMessage('points %s\n'%str(points))
                        lenpoints=len(points)
                        if lenpoints>=4 and lenpoints % 2 == 0:
                                lastvec = Vector(points[0],-points[1],0)
                                path=[]
                                if name == 'polygon':
                                        points=points+points[:2] # emulate closepath
                                for svgx,svgy in zip(points[2::2],points[3::2]):
                                        currentvec = Vector(svgx,-svgy,0)
                                        if not DraftVecUtils.equals(lastvec,currentvec):
                                                seg = Part.Line(lastvec,currentvec).toShape()
                                                #print "polyline seg ",lastvec,currentvec
                                                lastvec = currentvec
                                                path.append(seg)
                                if path:
                                        sh = Part.Wire(path)
                                        if self.fill and sh.isClosed():
                                            sh = Part.Face(sh)
                                        sh = self.applyTrans(sh)
                                        obj = self.doc.addObject("Part::Feature",pathname)
                                        obj.Shape = sh
                                        if self.currentsymbol:
                                            self.symbols[self.currentsymbol].append(obj)

                # processing ellipses

                if (name == "ellipse") :
                        if not pathname: pathname = 'Ellipse'
                        c = Vector(data.get('cx',0),-data.get('cy',0),0)
                        rx = data['rx']
                        ry = data['ry']
                        if rx > ry:
                                sh = Part.Ellipse(c,rx,ry).toShape()
                        else:
                                sh = Part.Ellipse(c,ry,rx).toShape()
                                m3=FreeCAD.Matrix()
                                m3.move(c)
                                rot90=FreeCAD.Matrix(0,-1,0,0,1,0) #90
                                m3=m3.multiply(rot90)
                                m3.move(c.multiply(-1))
                                sh.transformShape(m3)
                                #sh = sh.transformGeometry(m3)
                        if self.fill:
                                sh = Part.Wire([sh])
                                sh = Part.Face(sh)
                        sh = self.applyTrans(sh)
                        obj = self.doc.addObject("Part::Feature",pathname)
                        obj.Shape = sh
                        self.format(obj)
                        if self.currentsymbol:
                            self.symbols[self.currentsymbol].append(obj)


                # processing circles

                if (name == "circle") and (not ("freecad:skip" in data)) :
                        if not pathname: pathname = 'Circle'
                        c = Vector(data.get('cx',0),-data.get('cy',0),0)
                        r = data['r']
                        sh = Part.makeCircle(r)
                        if self.fill:
                                sh = Part.Wire([sh])
                                sh = Part.Face(sh)
                        sh.translate(c)
                        sh = self.applyTrans(sh)
                        obj = self.doc.addObject("Part::Feature",pathname)
                        obj.Shape = sh
                        self.format(obj)
                        if self.currentsymbol:
                            self.symbols[self.currentsymbol].append(obj)

                # processing texts

                if name in ["text","tspan"]:
                        if not("freecad:skip" in data):
                                FreeCAD.Console.PrintMessage("processing a text\n")
                                if 'x' in data:
                                        self.x = data['x']
                                else:
                                        self.x = 0
                                if 'y' in data:
                                        self.y = data['y']
                                else:
                                        self.y = 0
                                if 'font-size' in data:
                                        if data['font-size'] != 'none':
                                                self.text = getsize(data['font-size'],'css')
                                else:
                                        self.text = 1
                        else:
                                if self.lastdim:
                                        self.lastdim.ViewObject.FontSize = int(getsize(data['font-size']))
                                        
                # processing symbols
                
                if name == "symbol":
                    self.symbols[pathname] = []
                    self.currentsymbol = pathname
                    
                if name == "use":
                    if "xlink:href" in data:
                        symbol = data["xlink:href"][0][1:]
                        if symbol in self.symbols:
                            FreeCAD.Console.PrintMessage("using symbol "+symbol+"\n")
                            shapes = []
                            for o in self.symbols[symbol]:
                                if o.isDerivedFrom("Part::Feature"):
                                    shapes.append(o.Shape)
                            if shapes:
                                sh = Part.makeCompound(shapes)
                                v = FreeCAD.Vector(float(data['x']),-float(data['y']),0)
                                sh.translate(v)
                                sh = self.applyTrans(sh)
                                obj = self.doc.addObject("Part::Feature",symbol)
                                obj.Shape = sh
                                self.format(obj)
                        else:
                            FreeCAD.Console.PrintMessage("no symbol data\n")

                FreeCAD.Console.PrintMessage("done processing element %d\n"%self.count)
                
        def characters(self,content):
                if self.text:
                        FreeCAD.Console.PrintMessage("reading characters %s\n" % content)
                        obj=self.doc.addObject("App::Annotation",'Text')
                        obj.LabelText = content.encode('latin1')
                        if self.currentsymbol:
                            self.symbols[self.currentsymbol].append(obj)
                        vec = Vector(self.x,-self.y,0)
                        if self.transform:
                                vec = self.translateVec(vec,self.transform)
                                #print "own transform: ",self.transform, vec
                        for transform in self.grouptransform[::-1]:
                                #vec = self.translateVec(vec,transform)
                                vec = transform.multiply(vec)
                        #print "applying vector: ",vec
                        obj.Position = vec
                        if gui:
                                obj.ViewObject.FontSize = int(self.text)
                                if self.fill: obj.ViewObject.TextColor = self.fill
                                else: obj.ViewObject.TextColor = (0.0,0.0,0.0,0.0)

        def endElement(self, name):
            if not name in ["tspan"]:
                self.transform = None
                self.text = None
            if name == "g" or name == "svg":
                FreeCAD.Console.PrintMessage("closing group\n")
                self.grouptransform.pop()
            if name == "symbol":
                if self.doc.getObject("svgsymbols"):
                    group = self.doc.getObject("svgsymbols")
                else:
                    group = self.doc.addObject("App::DocumentObjectGroup","svgsymbols")
                for o in self.symbols[self.currentsymbol]:
                    group.addObject(o)
                self.currentsymbol = None
                    

        def applyTrans(self,sh):
                if isinstance(sh,Part.Shape):
                        if self.transform:
                                FreeCAD.Console.PrintMessage("applying object transform: %s\n" % self.transform)
                                #sh = transformCopyShape(sh,self.transform)
                                # see issue #2062
                                sh = sh.transformGeometry(self.transform)
                        for transform in self.grouptransform[::-1]:
                                FreeCAD.Console.PrintMessage("applying group transform: %s\n" % transform)
                                #sh = transformCopyShape(sh,transform)
                                # see issue 2062
                                sh = sh.transformGeometry(transform)
                        return sh
                elif Draft.getType(sh) == "Dimension":
                        pts = []
                        for p in [sh.Start,sh.End,sh.Dimline]:
                                cp = Vector(p)
                                if self.transform:
                                        FreeCAD.Console.PrintMessage("applying object transform: %s\n" % self.transform)
                                        cp = self.transform.multiply(cp)
                                for transform in self.grouptransform[::-1]:
                                        FreeCAD.Console.PrintMessage("applying group transform: %s\n" % transform)
                                        cp = transform.multiply(cp)
                                pts.append(cp)
                        sh.Start = pts[0]
                        sh.End = pts[1]
                        sh.Dimline = pts[2]

        def translateVec(self,vec,mat):
                v = Vector(mat.A14,mat.A24,mat.A34)
                return vec.add(v)

        def getMatrix(self,tr):
                "returns a FreeCAD matrix from a svg transform attribute"
                transformre=re.compile('(matrix|translate|scale|rotate|skewX|skewY)\s*?\((.*?)\)',re.DOTALL)
                m = FreeCAD.Matrix()
                for transformation, arguments in transformre.findall(tr):
                        argsplit=[float(arg) for arg in arguments.replace(',',' ').split()]
                        #m.multiply(FreeCAD.Matrix (1,0,0,0,0,-1))
                        #print '%s:%s %s %d' % (transformation, arguments,argsplit,len(argsplit))
                        if transformation == 'translate':
                                tx = argsplit[0]
                                ty = argsplit[1] if len(argsplit) > 1 else 0.0
                                m.move(Vector(tx,-ty,0))
                        elif transformation == 'scale':
                                sx = argsplit[0]
                                sy = argsplit[1] if len(argsplit) > 1 else sx
                                m.scale(Vector(sx,sy,1))
                        elif transformation == 'rotate':
                                angle = argsplit[0]
                                if len(argsplit) >= 3:
                                        cx = argsplit[1]
                                        cy = argsplit[2]
                                        m.move(Vector(cx,-cy,0))
                                m.rotateZ(math.radians(-angle)) #mirroring one axis equals changing the direction of rotaion
                                if len(argsplit) >= 3:
                                        m.move(Vector(-cx,cy,0))
                        elif transformation == 'skewX':
                                m=m.multiply(FreeCAD.Matrix(1,-math.tan(math.radians(argsplit[0]))))
                        elif transformation == 'skewY':
                                m=m.multiply(FreeCAD.Matrix(1,0,0,0,-math.tan(math.radians(argsplit[0]))))
                        elif transformation == 'matrix':
#                            '''transformation matrix:
#                                   FreeCAD                 SVG
#                                (+A -C +0 +E)           (A C 0 E)
#                                (-B +D -0 -F)  = (-Y) * (B D 0 F) *(-Y)
#                                (+0 -0 +1 +0)           (0 0 1 0)
#                                (+0 -0 +0 +1)           (0 0 0 1)'''
                                m=m.multiply(FreeCAD.Matrix(argsplit[0],-argsplit[2],0,argsplit[4],-argsplit[1],argsplit[3],0,-argsplit[5]))
                        #else:
                                #print 'SKIPPED %s' % transformation
                        #print "m= ",m
                #print "generating transformation: ",m
                return m

def decodeName(name):
        "decodes encoded strings"
        try:
                decodedName = (name.decode("utf8"))
        except UnicodeDecodeError:
                try:
                        decodedName = (name.decode("latin1"))
                except UnicodeDecodeError:
                        FreeCAD.Console.PrintError("svg: error: couldn't determine character encoding\n")

                        decodedName = name
        return decodedName

def getContents(filename,tag,stringmode=False):
        "gets the contents of all the occurences of the given tag in the given file"
        result = {}
        if stringmode:
                contents = filename
        else:
                f = pythonopen(filename)
                contents = f.read()
                f.close()
        contents = contents.replace('\n','_linebreak')
        searchpat = '<'+tag+'.*?</'+tag+'>'
        tags = re.findall(searchpat,contents)
        for t in tags:
                tagid = re.findall('id="(.*?)"',t)
                if tagid:
                        tagid = tagid[0]
                else:
                        tagid = 'none'
                res = t.replace('_linebreak','\n')
                result[tagid] = res
        return result

def open(filename):
        docname=os.path.split(filename)[1]
        doc=FreeCAD.newDocument(docname)
        doc.Label = decodeName(docname[:-4])
        parser = xml.sax.make_parser()
        parser.setContentHandler(svgHandler())
        parser._cont_handler.doc = doc
        f = pythonopen(filename)
        parser.parse(f)
        f.close()
        doc.recompute()
        return doc

def insert(filename,docname):
        try:
                doc=FreeCAD.getDocument(docname)
        except NameError:
                doc=FreeCAD.newDocument(docname)
        FreeCAD.ActiveDocument = doc
        parser = xml.sax.make_parser()
        parser.setContentHandler(svgHandler())
        parser._cont_handler.doc = doc
        parser.parse(pythonopen(filename))
        doc.recompute()

def export(exportList,filename):
        "called when freecad exports a file"

        svg_export_style = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetInt("svg_export_style")
        if svg_export_style != 0 and svg_export_style != 1:
            FreeCAD.Console.PrintMessage("unknown svg export style, switching to Translated\n")
            svg_export_style = 0

        # finding sheet size
        bb = None
        for ob in exportList:
                if ob.isDerivedFrom("Part::Feature"):
                    if bb:
                        bb.add(ob.Shape.BoundBox)
                    else:
                        bb = ob.Shape.BoundBox
        if bb:
            minx = bb.XMin
            maxx = bb.XMax
            miny = bb.YMin
            maxy = bb.YMax
        else:
            FreeCAD.Console.PrintError("The export list contains no shape\n")
            return
            
        if svg_export_style == 0:
            # translated-style exports get a bit of a margin
            margin = (maxx-minx)*.01
        else:
            # raw-style exports get no margin
            margin = 0

        minx -= margin 
        maxx += margin
        miny -= margin
        maxy += margin
        sizex = maxx-minx
        sizey = maxy-miny
        miny += margin

        # writing header
        # we specify the svg width and height in FreeCAD's physical units (mm),
        # and specify the viewBox so that user units maps one-to-one to mm
        svg = pythonopen(filename,'wb') 
        svg.write('<?xml version="1.0"?>\n')
        svg.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"')
        svg.write(' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
        svg.write('<svg')
        svg.write(' width="' + str(sizex) + 'mm" height="' + str(sizey) + 'mm"')
        if svg_export_style == 0:
            # translated-style exports have the viewbox starting at X=0, Y=0
            svg.write(' viewBox="0 0 ' + str(sizex) + ' ' + str(sizey) + '"')
        else:
            # raw-style exports have the viewbox starting at X=xmin, Y=-ymax
            # we need the funny Y here because SVG is upside down, and we
            # flip the sketch right-way up with a scale later
            svg.write(' viewBox="%f %f %f %f"' %(minx,-maxy,sizex,sizey))
        svg.write(' xmlns="http://www.w3.org/2000/svg" version="1.1"')
        svg.write('>\n')

        # writing paths
        for ob in exportList:
                if svg_export_style == 0:
                    # translated-style exports have the entire sketch translated to fit in the X>0, Y>0 quadrant
                    #svg.write('<g transform="translate('+str(-minx)+','+str(-miny+(2*margin))+') scale(1,-1)">\n')
                    svg.write('<g id="%s" transform="translate(%f,%f) '
                            'scale(1,-1)">\n'% (ob.Name,-minx,maxy))
                else:
                    # raw-style exports do not translate the sketch
                    svg.write('<g id="%s" transform="scale(1,-1)">\n' %\
                            ob.Name)
                svg.write(Draft.getSVG(ob))
                svg.write('<title>%s</title>\n' % ob.Label.encode('utf8')\
                        .replace('<','&lt;').replace('>','&gt;'))
                        # replace('"',\ "&quot;")
                svg.write('</g>\n')
        # closing
        svg.write('</svg>')
        svg.close()
