Benutzer:Alva2004/Programme/Svg

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen
#!/usr/bin/python
'''svg datei mit der funktionalitaet von xfig'''
import os, sys, textwrap, unicodedata
from math import atan2, copysign, cos, pi, sin, sqrt
from py2or3 import UserDict
from matfunc2 import Vec, cumtest # Vec fuer Bezier, Oval,RechterWinkel, Winkel
svgids = [] # benutzte oids
floatfmt = '%1.4f'
#==============================================================================
def wrap( txt ):
    return textwrap.fill(
        txt, width=78,
        subsequent_indent='    ', break_long_words=False,
        break_on_hyphens=False,
        # defaults:
        expand_tabs=True, replace_whitespace=True,
        drop_whitespace=True, initial_indent='',
        fix_sentence_endings=False
        ).strip() + '\n'
#==============================================================================
def char( name ):
    '''namen gibt gnome-characters: thin space, hair space, middle dot
    https://de.wikipedia.org/wiki/Unicodeblock_Mathematische_Operatoren etc
    '''
    oft = { "'": u'\u02bc',  # Modifier Letter Apostrophe
            '"': u'\u02ee',  # Modifier Letter Double Apostrophe
            ' ': u'\u200a',  # Hair Space
            'spherical angle': '\u2222', 'arc': '\u2312',
            'angle': '\u2220',  'right angle': '\u299c',
            'rectangle': '\u25ad', 'circle': '\u2b58', 'square': '\u25a1',
            'sun': '\u2609', 'ellipse': '\u2b2d', 'corner': '\u23a1',
            'line': '\u23bb', 'zigzag': '\u299a', 'spiral': '\u1aa4',
            'pentagon': '\u2b20', 'oval': '\u1b75', 'bezier': '\u0cee',
            'marker': '\u2197', # north east arrow
            'text': '\u2026', # ...
            'parallel': '\u2225', 'approx': '\u2248', 'equiv': '\u2263',
            'cdot': unicodedata.lookup( 'Black Slightly Small Circle' ),
            'top': '\u142a', 'infty': '\u221e',
            'C': '\u2102', 'N': '\u2115', 'Q': '\u211a', 'R': '\u211d',
            'Z': '\u2124', 'defs': '\u225d'
            }
    if name.lower() in oft:
        ans = oft[ name.lower() ]
    else:
        nok = 1
        try:
            ans = unicodedata.lookup( name )
            nok = 0
        except:
            pass
        if nok:
            ans = unicodedata.lookup( 'greek small letter %s'%name )
    return ans
#==============================================================================
class SvgCodes:
    "analog xfig-Codes, siehe auch https://www.farb-tabelle.de"
    SIGN='' # erstes zeichen in id
    LOOP=[ 0, 1, 12, 33, 4, 21, 27, 2, 11, 32, 24, 15, 1, 31, 3, 5, 6, 7, 8, 9,
           10, 13, 14, 16, 17, 18, 19, 20, 22, 23, 25, 26, 28, 29, 30 ]
    COLOR={ "BLACK":0, 'WHITE':7,
            'BLUE':1,'BLUE2':8,'CORNFLOWERBLUE': 8,
            'BLUE3':9,'BLUE4':10,'LTBLUE':11,'LIGHTBLUE':11,
            'GREEN':2, 'GREEN2':12,'GREEN3':13,'GREEN4':14,
            'CYAN':3, 'CYAN2':15,'CYAN3':16,'CYAN4':17,
            'RED':4, 'RED2':18,'RED3':19,'RED4':20,
            'MAGENTA':5, 'MAGENTA1':21,'MAGENTA2':22,'MAGENTA3':23,
            'BROWN':24,'BROWN1':25,'BROWN2':26,
            'PINK3':30,'PINK2':29,'PINK1':28,'PINK':27,
            'YELLOW':6, 'GOLD':31, 'LILA': 32, 'ORANGE': 33, 'GELBORANGE': 34,
            'BEIGE': 35, 'PURPLE': 36 }
    PEN={ 0:  "black",   # black
          1:  "blue",    # blue
          2:  "green",   # green
          3:  "cyan",    # cyan
          4:  "red",     # red
          5:  "magenta", # magenta
          6:  "yellow",  # yellow
          7:  "white",   # white
          8:  "cornflowerblue", # blue2 cornflowerblue
          9:  "#0000d0", # blue3
          10: "#0000b0", # blue4
          11: "lightblue", # ltblue
          12: "#8fff0e", # green2 gelbgruen
          13: "#00d000", # green3
          14: "#00b800", # green4
          15: "#00d0d0", # cyan2
          16: "#00b0b0", # cyan3
          17: "#009090", # cyan4
          18: "#ff4500", # red2 orangerot
          19: "#d00000", # red3
          20: "#b00000", # red4
          21: "#d000d0", # magenta2
          22: "#b000b0", # magenta3
          23: "#900090", # magenta4
          24: "brown",   # brown
          25: "#a04000", # brown2
          26: "#803000", # brown3
          27: "pink",    # pink
          28: "#ffc0c0", # pink2
          29: "#ffa0a0", # pink3
          30: "#ff8080", # pink4
          31: "gold",    # gold
          32: "#a020f0", # lila
          33: "orange",  # orange
          34: "#ffb90f", # gelborange darkgoldenrod1
          35: "#ceb673", # beige
          36: "purple",  # violett
          }
    LINESTYLE={ 'SOLID':0, 'DASHED':1, 'DOTTED':2, 'DASH-DOTTED':3 }
    DASH={ 0: "", 1: "40 40", 2: "10 30", 3: "30 15 10 15" }
    SPLINE={ 'OPEN APPROXIMATED SPLINE': 0,
             'CLOSED APPROXIMATED SPLINE': 1,
             'OPEN INTERPOLATED SPLINE': 2,
             'CLOSED INTERPOLATED SPLINE': 3,
             'OPEN X-SPLINE': 4,
             'CLOSED X-SPLINE': 5 }
    # Text
    JUSTIFICATION={ 'LEFT': 0, 'CENTER': 1, 'RIGHT': 2 }
    FONT={ 'Default font': -1, 'Times Roman': 0, 'Times Italic': 1,
           'Times Bold': 2, 'Times Bold Italic': 3, 'AvantGarde Book': 4,
           'AvantGarde Book Oblique': 5, 'AvantGarde Demi': 6,
           'AvantGarde Demi Oblique': 7, 'Bookman Light': 8,
           'Bookman Light Italic': 9, 'Bookman Demi': 10,
           'Bookman Demi Italic': 11, 'Courier': 12, 'Courier Oblique': 13,
           'Courier Bold': 14, 'Courier Bold Oblique': 15,
           'Helvetica': 16, 'Helvetica Oblique': 17, 'Helvetica Bold': 18,
           'Helvetica Bold Oblique': 19, 'Helvetica Narrow': 20,
           'Helvetica Narrow Oblique': 21, 'Helvetica Narrow Bold':22,
           'Helvetica Narrow Bold Oblique': 23,
           'New Century Schoolbook Roman': 24,
           'New Century Schoolbook Italic': 25,
           'New Century Schoolbook Bold': 26,
           'New Century Schoolbook Bold Italic': 27,
           'Palatino Roman': 28, 'Palatino Italic':29, 'Palatino Bold': 30,
           'Palatino Bold Italic': 31, 'Symbol': 32,
           'Zapf Chancery Medium Italic': 33, 'Zapf Dingbats': 34 }
    #--------------------------------------------------------------------------
    def getColor( self, code ):
        'gibt die farbe code, wenn es sie gibt'
        lt = str( code ).upper()
        if lt == 'NONE':
            ans = 'none'
        elif lt in self.COLOR:
            ans = self.PEN[ self.COLOR[ lt ] ]
        elif code in self.PEN:
            ans = self.PEN[ code ]
        elif ( lt[ 0 ] == '#'
               and len( lt ) == 7
               and all( [ s in '0123456789ABCDEF' for s in lt[ 1: ] ] ) ):
            ans = code
        else:
            raise ValueError( 'Farbe "%s" nicht erkannt'%code )
        return ans
    #--------------------------------------------------------------------------
    def getLineStyle( self, code ):
        'versucht linestyle code zu erkennen'
        lt  = str( code ).upper()
        if lt == 'SOLID':
            ans = None
        elif lt in self.LINESTYLE.keys():
            ans = self.DASH[ self.LINESTYLE[ lt ] ]
        elif code in self.DASH.keys():
            ans = self.DASH[ code ]
        else:
            ans = '%s'%( ' '.join( map( str, code ) ) )
        return ans
    #--------------------------------------------------------------------------
    def getSplineType( self, code ):
        "Art des splines"
        st  = str( code ).upper()
        ans = None
        if st in self.SPLINE.keys():
            ans = self.SPLINE[ st ]
        else:
            try:
                st = int( st )
                if st in self.SPLINE.values():
                    ans = st
            except:
                pass
        if ans == None:
            raise ValueError( 'SubType "%s" not in %s'%( code,
                                                         self.SPLINE ) )
        return ans
    #--------------------------------------------------------------------------
    def getOrientation( self, code ):
        orientation = code.capitalize()
        if not orientation in self.ORIENTATION:
            raise ValueError( 'Orientation "%s" not in %s'%(
                orientation, self.ORIENTATION ) )
        return orientation
    #--------------------------------------------------------------------------
    def getJustification( self, code ):
        just = code.capitalize()
        if just in self.JUSTIFICATION:
            pass
        elif just == 'Flush left':
            just = 'Flush Left'
        else:
            raise ValueError( 'Justifikation "%s" not in %s'%(
                just, self.JUSTIFICATION ) )
        return just
    #--------------------------------------------------------------------------
    def name( self ):
        return ( str( self.__class__ )
                 .replace( '__main__.', '' )
                 .replace( '<class', '' )
                 .replace( '>', '' )
                 .replace( "'", '' )
                 )
    #--------------------------------------------------------------------------
    def __lt__( a, b ):
        return False
    #--------------------------------------------------------------------------
#==============================================================================
class SvgObjekt( SvgCodes, UserDict ):
    'Objekt mit typ und evtl id'
    #--------------------------------------------------------------------------
    def __init__( self, typ='', oid='' ):
        global svgids
        UserDict.__init__( self, { 'typ': typ } )
        if not oid:
            oid = self.name()
        nr  = 0
        ori = oid = self.noApostroph( oid )
        while oid in svgids:
            nr += 1
            oid = '%s-%d'%( ori, nr )
        svgids.append( oid )
        self[ 'oid' ] = oid
    #--------------------------------------------------------------------------
    def noApostroph( self, txt ):
        'ersetzt " durch alternative'
        return ( txt.replace( "''", char( '"' ) )
                 .replace( '"', char( '"' ) )
                 .replace( "'", char( "'" ) ) )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt style'
        typ = self[ 'typ' ]
        #print( 'SvgObjekt.svgCode %s %s'%( self.name(), self[ 'id' ] ) )
        if typ == 'linearGradient':
            txt = '\n<%s '%typ
        else:
            txt = '\n<%s '%typ.lower()
        return txt + 'id="%s"\n'%self[ 'oid' ]
    #--------------------------------------------------------------------------
#==============================================================================
class Kommentar( SvgObjekt ):
    'ein Kommentar'
    #--------------------------------------------------------------------------
    def __init__( self, text='' ):
        SvgObjekt.__init__( self, typ='Kommentar', oid='Kommentar' )
        self.text = text
    #--------------------------------------------------------------------------
    def svgCode( self ):
        return wrap( '<!-- %s -->\n'%self.text )
    #--------------------------------------------------------------------------
#==============================================================================
class Primitive( SvgObjekt ):
    '''Graphisches objekt mit typ, id, x0, opacity und
    transformationen translate, rotate, scale
    '''
    #--------------------------------------------------------------------------
    def __init__( self, typ='', oid='', x0='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='' ):
        SvgObjekt.__init__( self, typ, oid )
        self[ 'x0' ] = x0
        if not opacity in ( '', 1 ):
            self[ 'opacity' ] = opacity
        if not fillopacity in ( '', 1 ):
            self[ 'fill-opacity' ] = fillopacity
        if not strokeopacity in ( '', 1 ):
            self[ 'stroke-opacity' ] = strokeopacity
        if matrix in ( '', [], () ):
            if not translate in ( '', ):
                self[ 'translate' ] = tuple( translate )
            if rotate != '':
                self[ 'rotate' ] = rotate
            if scale != '':
                self[ 'scale' ] = tuple( scale )
        else:
            self[ 'matrix' ] = tuple( matrix )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt style'
        txt = SvgObjekt.svgCode( self )
        for key in ( 'opacity', 'fill-opacity', 'stroke-opacity' ):
            if key in self:
                txt += '%s="%s" '%( key, self[ key ] )
        transform = ''
        if 'matrix' in self:
            transform += 'matrix(%s,%s,%s,%s,%s,%s)'%(
                6*( floatfmt, ) )%tuple( self[ 'matrix' ] )
        else:
            if 'translate' in self:
                transform += 'translate%s '%str( self[ 'translate' ] )
            if 'rotate' in self:
                x0 = self[ 'x0' ]
                if x0 == '':
                    transform +='rotate(%s) '%self[ 'rotate' ]
                else:
                    ( x,y ) = x0
                    transform +='rotate(%s, %s, %s) '%( self[ 'rotate' ],
                                                        x, y )
            if 'scale' in self:
                transform += 'scale%s '%str( self[ 'scale' ] )
        if transform:
            txt += 'transform="%s" '%transform.strip()
        return wrap( txt )
    #--------------------------------------------------------------------------
#==============================================================================
class Geometry( Primitive ):
    'Geometrisches objekt mit Eigenschaften einer Linie'
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  typ='', oid='', x0='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  url='', fill='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='' ):
        Primitive.__init__( self, typ=typ, oid=oid, x0=x0,
                            opacity=opacity, fillopacity=fillopacity,
                            strokeopacity=strokeopacity, matrix=matrix,
                            translate=translate, rotate=rotate, scale=scale )
        if stroke != '':
            self[ 'stroke' ] = stroke
        if strokewidth != '':
            self[ 'stroke-width' ] = strokewidth
        if url != '':
            self[ 'url' ] = url
        if fill != '':
            self[ 'fill' ] = fill
        if not linejoin in 'miter':
            self[ 'stroke-linejoin' ] = linejoin # miter-clip round bevel arcs
        if dasharray != '':
            self[ 'stroke-dasharray' ] = dasharray
        if markerstart != '':
            self[ 'marker-start' ] = markerstart
        if markermid != '':
            self[ 'marker-mid' ] = markermid
        if markerend != '':
            self[ 'marker-end' ] = markerend
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt style'
        txt = Primitive.svgCode( self )
        if 'stroke' in self:
            txt += 'stroke="%s" '%self.getColor( self[ 'stroke' ] )
        if 'url' in self:
            txt += 'fill="url(#%s)" '%self[ 'url' ]
        elif 'fill' in self:
            txt += 'fill="%s" '%self.getColor( self[ 'fill' ] )
        if 'stroke-width' in self:
            txt += 'stroke-width="%s" '%self[ 'stroke-width' ]
        if 'stroke-dasharray' in self:
            lt = self.getLineStyle( self[ 'stroke-dasharray' ] )
            if lt:
                txt += 'stroke-dasharray="%s" '%lt
        if 'stroke-linejoin' in self:
            txt += 'stroke-linejoin="%s" '%self[ 'stroke-linejoin' ]
        for pos in ( 'start', 'mid', 'end' ):
            key = 'marker-%s'%pos
            if key in self:
                txt += 'marker-%s="url(#%s)" '%( pos, self[ key ] )
        return wrap( txt )
    #--------------------------------------------------------------------------
#==============================================================================
class Container( SvgObjekt ):
    'defs,group,marker,svg'
    #--------------------------------------------------------------------------
    def __init__( self, typ='', oid='' ):
        SvgObjekt.__init__( self, typ, oid )
        self.objs = []
    #--------------------------------------------------------------------------
    def addObj( self, obj ):
        'fuegt svgobjekt obj hinzu'
        #print '%s.addObj %s'%( self[ 'typ' ], obj[ 'typ' ] )
        self.objs.append( obj )
    #--------------------------------------------------------------------------
    def preObj( self, obj ):
        'fuegt svgobjekt obj am Anfang hinzu'
        self.objs.insert( 1, obj )
    #--------------------------------------------------------------------------
    def addObjs( self, tup ):
        'fuegt svgobjekte aus tup hinzu'
        self.objs.extend( tup )
    #--------------------------------------------------------------------------
    def svgCodeOhneWrap( self ):
        'gibt den svg code der objekte in diesem container'
        txt = ''
        for obj in self.objs:
            txt += obj.svgCode()
        return txt + '\n'
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt den svg code der objekte in diesem container'
        txt = ''
        vb  = self[ 'viewBox' ] # von Svg
        for obj in self.objs:
            if isinstance( obj, Container ):
                obj[ 'viewBox' ] = vb
                txt += obj.svgCode()
            elif not isinstance( obj, Geometry ):
                txt += obj.svgCode() # kein wrap Image, LinearGradient, Stop
            else:
                if isinstance( obj, Line ):
                    obj[ 'viewBox' ] = vb
                txt += wrap( obj.svgCode() )
        return txt
    #--------------------------------------------------------------------------
#==============================================================================
class Defs( Container ):
    'wrapper, fuer objekte, die mit use dargestellt werden'
    SIGN=char( 'defs' )
    #--------------------------------------------------------------------------
    def __init__( self, oid='' ):
        Container.__init__( self, typ='Defs', oid=Defs.SIGN+oid )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt den svg code der objekte in diesem container'
        return ( '\n' + SvgObjekt.svgCode( self ).strip() + '>\n'
                 + Container.svgCode( self ) + '</defs>\n' )
    #--------------------------------------------------------------------------
#==============================================================================
class Group( Container, Geometry ):
    '<g>...</g> objekt'
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate=(0,0), x0='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # Group
                  **param ):
        #print 'Group param', param
        Container.__init__( self, typ='G', oid='{%s}'%oid )
        c = dict( self )
        Geometry.__init__(
            self, typ='G', x0=x0,
            opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            markerstart=markerstart, markermid=markermid, markerend=markerend )
        self.update( c )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt des svg code dieser group'
        txt  = '\n' + Geometry.svgCode( self ).strip() + '>\n'
        txt += Container.svgCode( self )
        txt += '</g><!-- %s -->\n'%self[ 'oid' ]
        return txt
    #--------------------------------------------------------------------------
#==============================================================================
class Bezier( Geometry ):
    'offene bezier kurve, geschlossene kurve siehe Oval'
    SIGN=char( 'bezier' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # Bezier
                  points=[] ):
        Geometry.__init__(
            self, typ='Path', oid='%s%s'%( Bezier.SIGN, oid ),
            x0=points[ 0 ], opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            markerstart=markerstart, markermid=markermid, markerend=markerend )
        self.update( { 'points': points } )
        self.getPath()
    #--------------------------------------------------------------------------
    def getPath( self ):
        'stores the oval as path'
        global cps
        pts = list( map( Vec, self[ 'points' ] ) )
        while len( pts ) > 2 and pts[ -2 ].dist( pts[ -1 ] ) < 1.e-6:
            pts.pop()
        while len( pts ) > 2 and pts[ 0 ].dist( pts[ 1 ] ) < 1.e-6:
            pts.pop( 0 )
        if len( pts ) > 2:
            tan = self.tangents( pts )
            cps = self.controlPoints( pts, tan )
            fmt = 'M %s %s '%tuple( 2*[ floatfmt ] ) 
            path = fmt%tuple( pts[ 0 ] )
            fmt = 'Q %s %s %s %s '%tuple( 4*[ floatfmt ] ) 
            for j in range( 1, len( pts ) ):
                p  = pts[ j ]
                cp = cps[ j ]
                path += fmt%( cp[ 0 ], cp[ 1 ], p[ 0 ], p[ 1 ] )
        else:
            fmt  = 'M %s %s L %s %s '%tuple( 4*[ floatfmt ] ) 
            path = fmt%( pts[ 0 ][ 0 ], pts[ 0 ][ 1 ],
                         pts[ 1 ][ 0 ], pts[ 1 ][ 1 ] )
        self[ 'd' ] = path
    #--------------------------------------------------------------------------
    def tangents( self, pts ):
        '''gives tangents to the oval in the points
        p = x0 + t*x1 + t**2*x2, p,t = x1 + 2*t*x2
        anfang und ende: tangente verlaengern und mittigen punkt zum endpunkt
        g = p1-p0, n = rot(g), 
        0.5*(r + s) + b*n = s + c*T1, ges: b,c |.T1 |.rot(T1)=R1
        gl1: 0.5*(r + s).T1 + b*n.T1 = s.T1 + c*T1.T1
        gl2: 0.5*(r + s).R1 + b*n.R1 = s.R1
        '''
        cum = [ 0,0 ]
        ( p0,p1 ) = ( r,s ) = pts[ :2 ]
        # tangents
        tan = []
        for p2 in pts[ 2: ]:
            tan.append( p2 - p0 )
            p0 = p1
            p1 = p2
        # tangente am anfang:
        T1 = tan[ 0 ]
        R1 = Vec( ( -T1[ 1 ], T1[ 0 ] ) )
        g  = s - r
        n  = Vec( ( -g[ 1 ], g[ 0 ] ) )
        gg = nn = g.dot( g )
        m  = 0.5*( r + s )
        ( ( nT1, T1T1, o, gT1 ), ( nR1, R1R1, o, gR1 ) ) = [
            [ u.dot( v ) for u in ( n,T1,R1,g ) ] for v in ( T1,R1 ) ]
        b = 0.5*gR1/nR1
        c = 0.5*( nT1*gR1 - nR1*gT1 )/( T1T1*nR1 )
        #cumtest( cum, 'tangents gl1', m + b*n, s + c*T1 )
        tan.insert( 0, s + c*T1 - r )
        # tangente am ende
        T1 = tan[ -1 ]
        R1 = Vec( ( -T1[ 1 ], T1[ 0 ] ) )
        g  = p1 - p0
        n  = Vec( ( -g[ 1 ], g[ 0 ] ) )
        m  = 0.5*( p1 + p0 )
        tup = ( n,g,T1 )
        ( ( nT1, gT1, T1T1 ), ( nR1, gR1, T1R1 ) ) = [
        [ v.dot( u ) for v in tup ] for u in ( T1,R1 ) ]
        if nR1 == 0: # m = p0 + c*T1 -> c = 0.5*gT1/T1T1
            b = 0
            c = 0.5*gT1/T1T1
        else:
            b = -0.5*gR1/nR1
            c = 0.5*( nR1*gT1 - nT1*gR1 )/( T1T1*nR1 )
        tan.append( p0 + c*T1 - p1 )
        return tan
    #--------------------------------------------------------------------------
    def controlPoints( self, pts, tan ):
        '''gives the controlpoints for bezier-curves
        pj: points, tj: tangents, tjr: rot(tj)
        p0 + a*t0 = p1 + b*t1
        -> a = ( p1 - p0 ).t1r/t0.t1r, b = ( p0 - p1 ).t0r/t1.t0r
        '''
        #......................................................................
        def rot( v ):
            'gives the vector perpendicular to v'
            return Vec( [ -v[ 1 ], v[ 0 ] ] )
        #......................................................................
        cps = []
        ( p0, t0 ) = ( pts[ -1 ], tan[ -1 ] )
        for j in range( len( pts ) ):
            ( p1, t1 ) = ( pts[ j ], tan[ j ] )
            t1r = rot( t1 )
            skp = t0.dot( t1r )
            if skp != 0:
                a = t1r.dot( p1 - p0 )/skp
            else:
                a = 0.5
            p = p0 + a*t0
            cps.append( p )
            ( p0, t0 ) = ( p1, t1 )
        return cps
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt den svg code dieses ovals'
        #self.getPath()
        txt  = Geometry.svgCode( self )
        txt += 'd="%s" />\n'%self.pop( 'd' )
        return txt
    #--------------------------------------------------------------------------
#==============================================================================
class Datei:
    'fuegt text aus datei ein'
    #--------------------------------------------------------------------------
    def __init__( self, name='', svg=None ):
        self.name = name
        fid = open( name, 'r' )
        self.txt = fid.read()
        fid.close()
        vb = self.getViewBox()
        if svg and len( vb ) == 4:
            self.merge( vb, svg )
    #--------------------------------------------------------------------------
    def trim( self, txt ):
        'cuts off <svg> definitions'
        j0 = txt.find( '<svg ' )
        if j0 > -1:
            txt = txt[ txt.find( '>', j0 )+1: ]
        j0 = txt.find( '</svg>' )
        if j0 > -1:
            txt = txt[ :j0 ]
        return txt
    #--------------------------------------------------------------------------
    def getViewBox( self ):
        j1 = j2 = -1
        j0 = self.txt.find( 'viewBox' )
        ans = []
        if j0 > -1:
            j1 = self.txt.find( '"', j0 ) + 1
            j2 = self.txt.find( '"', j1 )
            ans = [ float( s ) for s in self.txt[ j1:j2 ].split() ]
        print( 'getViewBox', j0,j1,j2 )
        return ans
    #--------------------------------------------------------------------------
    def merge( self, vb, svg ):
        ( x1,y1,b1,h1 ) = vb
        ( x0,y0,b0,h0 ) = svg[ 'viewBox' ]
        if x1 < x0:
            b0 += x0 - x1
            x0 = x1
        if x1 + b1 > x0 + b0:
            b0 = x1 + b1 - x0
        if y1 < y0:
            h0 += y0 - y1
            y0 = y1
        if y1 + h1 > y0 + h0:
            h0 = y1 + h1 - y0
        svg[ 'viewBox' ] = viewBox = ( x0,y0,b0,h0 )
        svg.param = ( b0, h0, viewBox, svg.param[ 3 ] )
        for j in range( len( svg.objs ) ):
            obj = svg.objs[ j ]
            if isinstance( obj, Rect ) and obj[ 'oid' ].endswith( 'bg' ):
                svg.objs[ j ] = Rect(
                    oid='bg',
                    x=viewBox[ 0 ], y=viewBox[ 1 ], width=viewBox[ 2 ],
                    height=viewBox[ 3 ], stroke='white', fill='white' )
                break
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt den svg code in dieser datei'
        txt = Kommentar( 'begin %s'%self.name ).svgCode()
        txt += self.trim( self.txt ).strip() + '\n'
        txt += Kommentar( 'end %s'%self.name ).svgCode()
        return txt
    #--------------------------------------------------------------------------
#==============================================================================
class Circle( Geometry ):
    SIGN=char( 'sun' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  # Circle
                  cx='', cy='', r='' ):
        Geometry.__init__(
            self, typ='Circle', oid='%s%s'%( Circle.SIGN, oid ),
            x0=(cx,cy), opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale, 
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin )
        self.update( { 'cx': cx, 'cy': cy, 'r': r } )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt des svg code dieses circle'
        fmt = 'cx="%s" cy="%s" r="%s" />\n'%tuple( 3*[ floatfmt ] ) 
        txt = Geometry.svgCode( self )
        ( cx, cy, r ) = [ self[ key ] for key in ( 'cx', 'cy', 'r' ) ]
        txt += fmt%( cx,cy,r )
        return txt
    #--------------------------------------------------------------------------
#==============================================================================
class Ellipse( Geometry ):
    SIGN=char( 'ellipse' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  # Ellipse
                  cx='', cy='', rx='', ry='' ):
        Geometry.__init__(
            self, typ='Ellipse', oid=Ellipse.SIGN+oid,
            x0=(cx,cy), opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale, 
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin )
        self.update( { 'cx': cx, 'cy': cy, 'rx': rx, 'ry': ry } )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt des svg code dieses ellipse'
        fmt = 'cx="%s" cy="%s" rx="%s" ry="%s" />\n'%tuple( 4*[ floatfmt ] ) 
        txt = Geometry.svgCode( self )
        txt += fmt%( self[ 'cx' ], self[ 'cy' ], self[ 'rx' ], self[ 'ry' ] )
        return txt
    #--------------------------------------------------------------------------
#==============================================================================
class Image( Primitive ):
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='', opacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Image
                  name='', x='', y='', width='', height='',
                  preserveAspectRatio=''
                  ):
        Primitive.__init__(
            self, typ='Image', oid=oid, x0=(x,y),
            opacity=opacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale )
        self.update( { 'name': name, 'x': x, 'y': y, 'width': width,
                       'height': height,
                       'preserveAspectRatio': preserveAspectRatio } )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt den svg code dieses image'
        [ oid, path, x, y, width, height, aspect ] = [ self[ key ] for key in (
            'oid', 'name', 'x', 'y', 'width', 'height', 'preserveAspectRatio')]
        txt = wrap( Primitive.svgCode( self )
                    + 'x="%s" y="%s" width="%s" height="%s"'%(
            x,y,width,height ) )
        ( rumpf, ext ) = os.path.splitext( path )
        ext = ext.replace( '.', '' )
        pip = os.popen( '{ base64 %s; } 2>&1'%path )
        img = pip.read().replace( ' ', '' ).replace( '\n', '' )
        sts = pip.close()
        txt += textwrap.fill( 'xlink:href="data:image/%s;base64,%s"'%(
            ext, img ), width=78, initial_indent='    ',
                              subsequent_indent='    ' ) + '/>\n'
        txt += Kommentar( 'Ende <image> "%s"'%oid ).svgCode()
        return txt
    #--------------------------------------------------------------------------
#==============================================================================
class Line( Geometry ):
    'offener linienzug'
    SIGN=char( 'line' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # Line
                  x1='', y1='', x2='', y2='', extend=0 ): # maximieren
        Geometry.__init__(
            self, typ='Line', oid=Line.SIGN+oid,
            x0=( x1, y1 ), opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            markerstart=markerstart, markermid=markermid, markerend=markerend )
        self.update( { 'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2,
                       'extend': extend } )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt den svg code dieser polyline'
        if self[ 'extend' ]: # erweitern zum umkreis der viewBox<-Svg.write
            from matfunc2d import Gerade, Kreis, Punkt
            ( x1, y1, x2, y2, vb ) = [ self[ key ] for key in
                                       ( 'x1', 'y1', 'x2', 'y2', 'viewBox' ) ]
            ( x0, y0, w, h ) = vb
            k = Kreis( Punkt( x0 + 0.5*w, y0 + 0.5*h ),
                       0.5*sqrt( w**2 + h**2 ) )
            tup = k.schnittPunkteGerade( Gerade( Punkt( x1,y1 ),
                                                 Punkt( x2,y2)))
            if len( tup ) == 2:
                ( x1,y1 ) = tup[ 0 ].p2t()
                ( x2,y2 ) = tup[ 1 ].p2t()
                self.update( dict( zip( ( 'x1', 'y1', 'x2', 'y2' ),
                                        ( x1, y1, x2, y2 ) ) ) )
        txt = Geometry.svgCode( self )
        fmt = '%%s="%s" '%floatfmt
        for key in ( 'x1', 'y1', 'x2', 'y2' ):
            txt += fmt%( key, self[ key ] )
        return txt + '/>\n'
    #--------------------------------------------------------------------------
#==============================================================================
class LinearGradient( Primitive ):
    UNITS=( 'objectBoundingBox', 'userSpaceOnUse' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  # LinearGradient
                  x1='', y1='', x2='', y2='', stops=[],
                  gradientUnits='', gradientTransform=''
                  ):
        Primitive.__init__( self, typ='linearGradient', oid=oid )
        self.update( { 'gradientUnits': gradientUnits,
                       'gradientTransform': gradientTransform,
                       'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2, 'stops': stops
                       })
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt des svg code dieses gradient'
        [ x1, y1, x2, y2, stops, gu, gt ] = [
            self[ key ] for key in ( 'x1', 'y1', 'x2', 'y2', 'stops',
                                     'gradientUnits', 'gradientTransform' ) ]
        txt = Primitive.svgCode( self )
        if gu:
            txt += 'gradientUnits="%s"\n'%gu
        if gt:
            txt += 'gradientTransform="%s"\n'%gt
        txt = wrap( txt
                    + 'x1="%s" y1="%s" x2="%s" y2="%s">\n'%( x1, y1, x2, y2 ) )
        for stop in stops:
            txt += stop.svgCode()
        return txt + '</linearGradient>\n'
    #--------------------------------------------------------------------------
#==============================================================================
class Marker( Container, UserDict ):
    SIGN=char( 'marker' )
    #--------------------------------------------------------------------------
    def __init__( self, # Marker
                  oid='', orient='auto', refX='', refY='',
                  markerWidth='', markerHeight='', viewBox=[] ):
        Container.__init__( self, oid=oid )
        UserDict.__init__( self, {
            'typ': 'Marker', 'oid': Marker.SIGN+self.noApostroph( oid ), 'orient': orient,
            'refX': refX, 'refY': refY, 'viewBox': viewBox,
            'markerWidth': markerWidth, 'markerHeight': markerHeight } )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt den svg code dieses markers'
        ( x, y, w, h ) = self[ 'viewBox' ]
        txt  = '<marker id="%s" viewBox="%1.0f %1.0f %1.0f %1.0f"\n'%(
            self[ 'oid' ], x, y, w, h )
        for key in ( 'orient', 'refX', 'refY', 'markerWidth', 'markerHeight' ):
            txt += '%s="%s" '%( key, self[ key ] )
        txt = wrap( txt + '>' )
        txt += Container.svgCode( self )
        return txt + '</marker>\n'
    #--------------------------------------------------------------------------
#==============================================================================
class Oval( Bezier ):
    'geschlossene bezier kurve'
    SIGN=char( 'oval' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # Bezier
                  points=[] ):
        Bezier.__init__(
            self, oid=Oval.SIGN+oid,
            opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            markerstart=markerstart, markermid=markermid, markerend=markerend,
            points=points )
    #--------------------------------------------------------------------------
    def getPath( self ):
        'stores the oval as path'
        pts = list( map( Vec, self.pop( 'points' ) ) )
        tan = self.tangents( pts )
        cps = self.controlPoints( pts, tan )
        fmt = 'M %s %s '%tuple( 2*[ floatfmt ] ) 
        path = fmt%tuple( pts[ -1 ] )
        fmt = 'Q %s %s %s %s '%tuple( 4*[ floatfmt ] ) 
        for j in range( len( pts ) ):
            p  = pts[ j ]
            cp = cps[ j ]
            path += fmt%( cp[ 0 ], cp[ 1 ], p[ 0 ], p[ 1 ] )
        self[ 'd' ] = path + 'z'
    #--------------------------------------------------------------------------
    def tangents( self, pts ):
        'gives tangents to the oval in the points'
        ( p0, p1 ) = pts[ -2: ]
        # tangents
        tan = []
        for p2 in pts:
            tan.append( p2 - p0 )
            p0 = p1
            p1 = p2
        tan.append( tan.pop( 0 ) )
        return tan
    #--------------------------------------------------------------------------
    def controlPoints( self, pts, tan ):
        '''gives the controlpoints for bezier-curves
        pj: points, tj: tangents, tjr: rot(tj)
        p0 + a*t0 = p1 + b*t1
        -> a = ( p1 - p0 ).t1r/t0.t1r, b = ( p0 - p1 ).t0r/t1.t0r
        '''
        #......................................................................
        def rot( v ):
            'gives the vector perpendicular to v'
            return Vec( [ -v[ 1 ], v[ 0 ] ] )
        #......................................................................
        cps = []
        ( p0, t0 ) = ( pts[ -1 ], tan[ -1 ] )
        for j in range( len( pts ) ):
            ( p1, t1 ) = ( pts[ j ], tan[ j ] )
            t1r = rot( t1 )
            skp = t0.dot( t1r )
            if skp != 0:
                a = t1r.dot( p1 - p0 )/skp
            else:
                a = 0.5
            p = p0 + a*t0
            cps.append( p )
            ( p0, t0 ) = ( p1, t1 )
        return cps
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt den svg code dieses ovals'
        self.getPath()
        txt  = Geometry.svgCode( self )
        txt += 'd="%s" />\n'%self.pop( 'd' )
        return txt
    #--------------------------------------------------------------------------
#==============================================================================
class Point( Circle ):
    SIGN='.'
    #--------------------------------------------------------------------------
    def __init__( self,
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  # Circle
                  cx='', cy='', r='' ):
        Circle.__init__(
            self, opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale, 
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            cx=cx, cy=cy, r=r )
        self[ 'oid' ] = '.'+self.noApostroph( oid )
    #--------------------------------------------------------------------------
#==============================================================================
class Polygon( Geometry ):
    'geschlossener linienzug'
    SIGN=char( 'pentagon' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # Polygon
                  points=[] ):
        Geometry.__init__(
            self, typ='Polygon', oid=Polygon.SIGN+oid,
            x0=points[ 0 ], opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            markerstart=markerstart, markermid=markermid, markerend=markerend )
        self.update( { 'points': points } )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt den svg code dieses polygons'
        txt = Geometry.svgCode( self ) + 'points="'
        fmt = '%s,%s '%tuple( 2*[ floatfmt ] )
        for ( x,y ) in self[ 'points' ]:
            txt += fmt%( x,y )
        return txt + '"/>\n'
    #--------------------------------------------------------------------------
#==============================================================================
class Polyline( Geometry ):
    'offener linienzug'
    SIGN=char( 'corner' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # Polyline
                  points=[] ):
        Geometry.__init__(
            self, typ='Polyline', oid=Polyline.SIGN+oid,
            x0=points[ 0 ], opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            markerstart=markerstart, markermid=markermid, markerend=markerend )
        self.update( { 'points': points } )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt den svg code dieser polyline'
        txt = Geometry.svgCode( self ) + 'points="'
        fmt = '%s,%s '%tuple( 2*[ floatfmt ] ) 
        for ( x,y ) in self[ 'points' ]:
            txt += fmt%( x,y )
        return txt + '"/>\n'
    #--------------------------------------------------------------------------
#==============================================================================
class RechterWinkel( Polyline ):
    'zeichnet rechtenwinkel bei p0 mit schenkeln nach p1 und p2'
    SIGN=char( 'right angle' )
    #--------------------------------------------------------------------------
    def __init__( self,
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # laenge r, drei punkte auf schenkeln durch p0
                  r=0, points=[] ):
        ( p0, p1, p2 ) = points
        ( v0, v1, v2 ) = map( Vec, ( p0, p1, p2 ) )
        a = v0 + ( v1 - v0 ).normalize()*r
        b = v0 + ( v2 - v0 ).normalize()*r
        c = a + ( v2 - v0 ).normalize()*r
        Polyline.__init__( self, opacity=opacity,
            fillopacity=fillopacity, strokeopacity=strokeopacity,
            matrix=matrix, translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url, stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            markerstart=markerstart, markermid=markermid, markerend=markerend,
            points=[ ( a[0], a[1] ), ( c[0], c[1] ), ( b[0], b[1] ) ] )
        self[ 'oid' ] = RechterWinkel.SIGN+self.noApostroph( oid )
    #--------------------------------------------------------------------------
#==============================================================================
class Rect( Geometry ):
    SIGN=char( 'square' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  # Rect
                  x='', y='', width='', height='', rx='', ry=''
                  ):
        Geometry.__init__(
            self, typ='Rect', oid=Rect.SIGN+oid,
            x0=(x,y), opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin )
        self.update( { 'x': x, 'y': y, 'width': width, 'height': height } )
        if rx:
            self[ 'rx' ] = rx
        if ry:
            self[ 'ry' ] = ry
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt des svg code dieses rect'
        txt = Geometry.svgCode( self )
        fmt = 'x="%s" y="%s" width="%s" height="%s" '%tuple( 4*[ floatfmt ] ) 
        txt += fmt%tuple(
            [ self[ key ] for key in ( 'x', 'y', 'width', 'height' ) ] )
        if 'rx' in self:
            txt += ( 'rx="%s" '%floatfmt )%self[ 'rx' ]
        if 'ry' in self:
            txt += ( 'ry="%s" '%floatfmt )%self[ 'ry' ]
        return txt + '/>\n'
    #--------------------------------------------------------------------------
#==============================================================================
class Path( Geometry ):
    'path'
    SIGN=char( 'zigzag' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # Path
                  d='' ):
        x0 = ( 0,0 )
        try: # d="M x y ..."
            x0 = list( map( float,
                            d.lower().replace( 'm', '' ).split()[ :2 ] ) )
            if len( x0 ) != 2:
                raise ValueError( 'Startpunkt des Pfades "%s"'%d
                                  + 'nicht erkannt.' )
        except:
            if d != '':
                print( 'Startpunkt des Pfades "%s" nicht erkannt.'%d )
            pass
        #print 'Path x0=', x0
        Geometry.__init__(
            self, typ='Path', oid=Path.SIGN+oid, x0=x0,
            opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            markerstart=markerstart, markermid=markermid, markerend=markerend )
        self.update( { 'd': d } )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt style'
        txt  = Geometry.svgCode( self )
        txt += 'd="%s"/>\n'%self[ 'd' ]
        return txt
    #--------------------------------------------------------------------------
#==============================================================================
class Bogen( Path ):
    'path'
    SIGN=char( 'arc' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # Bogen
                  x0='', r='', x1='', frot=0, flarge=0, fsweep=0 ):
        '''x0: startpunkt des bogens=tuple, x1: endpunkt=tuple, r: Radius'''
        d = 'M %1.0f %1.0f '%( x0[ 0 ], x0[ 1 ] )
        Path.__init__(
            self, opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            markerstart=markerstart, markermid=markermid, markerend=markerend )
        self.update( {'d': self.getBogen( d, r, x1, frot, flarge, fsweep ),
                      'oid': Bogen.SIGN+self.noApostroph( oid ) } )
    #--------------------------------------------------------------------------
    def getBogen( self, d, r, x1, frot, flarge, fsweep ):
        '''M x0 y0 A rx ry x-axis-rotation large-arc-flag sweep-flag x1 y1
        '''
        fmt = 'A %s %s %%d %%d %%d %s %s />\n'%tuple( 4*[ floatfmt ] ) 
        return d + fmt%( r, r, frot, flarge, fsweep, x1[ 0 ], x1[ 1 ] )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt style'
        txt  = Geometry.svgCode( self )
        txt += 'd="%s"/>\n'%self[ 'd' ]
        return txt
    #--------------------------------------------------------------------------
#==============================================================================
class Stop( Primitive ):
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  # Stop
                   offset='', color='', opacity=1
                  ):
        Primitive.__init__( self, typ='Stop' )
        self.update( { 'offset': offset, 'color': color, 'opacity': opacity } )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt des svg code dieses gradient'
        txt = Primitive.svgCode( self )
        [ offset, color, opacity ] = [
            self[ key ] for key in [ 'offset', 'color', 'opacity' ] ]
        txt += 'offset="%s" stop-color="%s" stop-opacity="%s" />\n'%(
            offset, self.getColor( color ), opacity )
        return wrap( txt )
    #--------------------------------------------------------------------------
#==============================================================================
class Text( Geometry ):
    '''transform="rotate(alp)"
    falls matrix=(1,0,0,-1,0,h) in svg angegeben, hier (1,0,0,-1,0,2*y) setzen
    font-family in Times, Helvetica, Symbol
    font-weight in normal, bold
    font-style  in normal, italic
    stroke: umrandung
    fill: textfarbe
    Beispiel: font="helvetica-bold-italic"
    font-size*1.5=px
    text-anchor in start, middle, end
    '''
    SIGN=char( 'text' )
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  # Text
                  anchor='middle',  # start, middle, end
                  family='helvetica', # times, helvetica, symbol
                  weight='',  # normal, bold
                  style='',   # normal, italic
                  x='', y='', size=12, label='' ):
        Geometry.__init__(
            self, typ='Text', oid=Text.SIGN+oid, x0=( x,y ),
            opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin )
        self.update( { 'x': x, 'y': y, 'size': size, 'label': label,
                       'family': family, 'style': style, 'weight': weight,
                       'anchor': anchor } )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt des svg code dieses text'
        txt = Geometry.svgCode( self )
        f   = floatfmt
        ( x, y, label, size, anchor, family, style, weight ) = [
            self[ key ] for key in
            ( 'x','y','label','size','anchor','family','style','weight' ) ]
        txt += ( 'x="%s" y="%s" text-anchor="%%s" '%( f,f ) )%( x, y, anchor )
        txt += ( 'font-size="%s" font-family="%%s" '%f )%( size, family )
        if style == 'italic':
            txt += 'font-style="italic" '
        if weight == 'bold':
            txt += 'font-weight="bold" '
        if ' ' in label:
            txt += 'xml:space="preserve" '
        txt += '>%s</text>\n'%label
        return txt
    #--------------------------------------------------------------------------
#==============================================================================
class TextFeld( Group ):
    'eine gruppe aus rect und text'
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate=(0,0), rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  # Text
                  anchor='middle',  # start, middle, end
                  family='helvetica', # times, helvetica, symbol
                  weight='',  # normal, bold
                  style='',   # normal, italic
                  x='', y='', size=12, label='',
                  # Rect
                  rx='', ry='' ):
        Group.__init__( self, oid=oid, x0=( x,y ), matrix=matrix,
                           translate=translate, rotate=rotate, scale=scale )
        ( dx,dy,w,h ) = self.getRect( label, size, anchor )
        if opacity != '':
            opacity=float( opacity )
        else:
            opacity = 0.5
        self.addObjs( ( Rect( oid=oid, opacity=opacity,
                              url=url, fill='white', rx=rx, ry=ry,
                              x=x+dx, y=y-0.8*size+dy, width=w, height=h ),
                        Text( oid=oid, strokeopacity=strokeopacity,
                              fillopacity=fillopacity, strokewidth=strokewidth,
                              fill=fill, stroke=stroke, dasharray=dasharray,
                              linejoin=linejoin, x=x, y=y, size=size,
                              label=label, family=family, style=style,
                              weight=weight, anchor=anchor ) ) )
    #--------------------------------------------------------------------------
    def getRect( self, txt, size, anchor ):
        dx = -0.33*size
        dy = 0.05*size
        if anchor == 'start':
            dx = 0
        elif anchor == 'end':
            dx = -0.6*size
        n  = len( txt )
        m  = sum( [ s in "'\",.:!" for s in txt ] )
        l  = ( n - 0.5*m )*0.65*size
        fp = any( [ s in 'gjpqy' for s in txt ] ) # unten
        fh = any( [ s.isupper() or s in 'bdfhklt' for s in txt ] ) # oben
        if fp and fh: # feld voll ausgenutzt
            height = size
        else:
            height = 0.8*size
            if fp:
                dy += 0.2*size
        return ( dx, dy, l, height )
    #--------------------------------------------------------------------------
#==============================================================================
class Use( Geometry ):
    '<use ... > fuer mehrfache verwendung von objekten insbes. groups'
    #--------------------------------------------------------------------------
    def __init__( self, # Primitive
                  typ='', oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # Use
                  x='', y='', href='' ):
        Geometry.__init__(
            self, typ='Use', oid=oid, x0=( x,y ),
            opacity=opacity, fillopacity=fillopacity,
            strokeopacity=strokeopacity, matrix=matrix,
            translate=translate, rotate=rotate, scale=scale,
            fill=fill, url=url,
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            markerstart=markerstart, markermid=markermid, markerend=markerend )
        self.update( { 'href': href, 'x': x, 'y': y } )
    #--------------------------------------------------------------------------
    def svgCode( self ):
        'gibt des svg code dieses use'
        txt = Geometry.svgCode( self )
        f = floatfmt
        txt += 'xlink:href="#%s" '%self[ 'href' ]
        txt += ( 'x="%s" y="%s" '%( f,f ) )%( self[ 'x' ], self[ 'y' ] )
        return txt + '/>\n'
    #--------------------------------------------------------------------------
#==============================================================================
class Winkel( Group ):
    'zeichnet winkel mit kreisbogen bei p0 mit schenkeln nach p1 und p2'
    #--------------------------------------------------------------------------
    def __init__( self, oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # laenge r, drei punkte auf schenkeln durch p0
                  points=[], r=0, text=None, dx=(0,0) ):
        '''line: None oder Polyline, points werden gesetzt
        fill: "" oder "#abcdef",
        txt: None oder Text, x und y werden gesetzt
        dx: abstand von txt von points[ 0 ]'''
        Group.__init__( self, oid=oid,
                        matrix=matrix, translate=translate, rotate=rotate,
                        scale=scale, x0=points[ 1 ] )
        ( v0, kontur ) = self.pfad( r, points )
        if fill:
            self.addObj( Polygon(
                oid=oid, fillopacity=fillopacity,
                # Geometry
                fill=fill, url=url, strokewidth=0,
                # Polygon
                points=[ v0 ] + kontur ) )
        self.addObj( Polyline(
            oid=oid, opacity=opacity,
            strokeopacity=strokeopacity,
            # Geometry
            stroke=stroke, strokewidth=strokewidth,
            dasharray=dasharray, linejoin=linejoin,
            markerstart=markerstart, markermid=markermid, markerend=markerend,
            # Polyline
            points=kontur ) )
        if isinstance( text, Text ):
            v = sum( kontur, v0 )
            s = Vec( [ x/float( len( kontur ) + 1 ) for x in v ] )
            text.update( { 'x': s[ 0 ] + dx[ 0 ], 'y': s[ 1 ] + dx[ 1 ],
                           'oid': Text.SIGN+self.noApostroph( oid ) } )
            self.addObj( text )
    #--------------------------------------------------------------------------
    def pfad( self, r, points ):
        'gibt den ort und den bogen des winkels'
        ( p0, p1, p2 ) = points
        ( v0, v1, v2 ) = map( Vec, ( p0, p1, p2 ) )
        a = ( v1 - v0 ).normalize()*r # |a|=r
        b = ( v2 - v0 ).normalize()*r # |b|=r
        # b=x*a+y*n |.a |.n
        x = a.dot( b )/r**2
        n = ( b - a*x ).normalize()*r # |n|=r
        y = b.dot( n )/r**2
        # b=a*cos(x)+n*sin(x)
        u = 0.
        v = atan2( b[ 1 ], b[ 0 ] ) - atan2( a[ 1 ], a[ 0 ] ) # endwinkel
        if v < 0:
            v += pi + pi
        l = max( 1, int( ( v - u )/( 0.02*pi ) ) )
        w = ( v - u )/l
        ans = ( v0, [ v0 + a*cos( j*w ) + n*sin( j*w ) for j in range( l+1 ) ])
        if 0:
            print( 'pfad1=0?', a.dot( n ) )
            print( 'pfad2=(0,0)?', a*x + n*y - b )
            print( 'pfada=(0,0)?', ans[ 1 ][ 0 ] - v0 - a )
            print( 'pfadb=(0,0)?', ans[ 1 ][ -1 ] - v0 - b )
        return ans
    #--------------------------------------------------------------------------
#==============================================================================
class WinkelBogen( Group ):
    'zeichnet winkel mit kreisbogen als path von p1 nach p2'
    #--------------------------------------------------------------------------
    def __init__( self, oid='',
                  opacity='', fillopacity='', strokeopacity='',
                  matrix='', translate='', rotate='', scale='',
                  # Geometry
                  fill='', url='',
                  stroke='', strokewidth='', dasharray='', linejoin='',
                  markerstart='', markermid='', markerend='',
                  # Bogen
                  points=[], r=0, frot=0, flarge=0, fsweep=0,
                  # Text
                  text=None, dx=(0,0) ):
        'Group aus Bogen und Text'
        ( x0,x1 ) = self.schenkel( points,r )
        Group.__init__( self, oid=oid, x0=x0,
                        matrix=matrix, translate=translate, rotate=rotate,
                        scale=scale, opacity=opacity )
        self.addObj( Bogen( oid=oid,fillopacity=fillopacity,
                            strokeopacity=strokeopacity,fill=fill, url=url,
                            stroke=stroke, strokewidth=strokewidth,
                            dasharray=dasharray, linejoin=linejoin,
                            markerstart=markerstart, markermid=markermid,
                            markerend=markerend, x0=x0, r=r, x1=x1, 
                            frot=frot, flarge=flarge, fsweep=fsweep ) )
        if isinstance( text, Text ):
            text.update( { 'x': x0[ 0 ] + dx[ 0 ], 'y': x0[ 1 ] + dx[ 1 ],
                           'oid': Text.SIGN+self.noApostroph( oid ) } )
            self.addObj( text )
    #--------------------------------------------------------------------------
    def schenkel( self, points,r ):
        'gibt die endpunkte des bogens des winkels'
        ( p0, p1, p2 ) = points
        ( v0, v1, v2 ) = map( Vec, ( p0, p1, p2 ) )
        a = ( v1 - v0 ).normalize()*r # |a|=r
        b = ( v2 - v0 ).normalize()*r # |b|=r
        return ( v0+a,v0+b )
    #--------------------------------------------------------------------------
#==============================================================================
class Svg( Group ):
    """schreibt svg"""
    #--------------------------------------------------------------------------
    def __init__( self, width=400, height=400, viewBox=[-200,-200,400,400],
                  rand=0, fmt='%1.0f' ):
        global floatfmt
        floatfmt = fmt
        Group.__init__( self, typ='Svg', oid='all', fill='none' )
        self.addObj( Rect(
            oid='bg',
            x=viewBox[ 0 ], y=viewBox[ 1 ], width=viewBox[ 2 ],
            height=viewBox[ 3 ], stroke='white', fill='white' ) )
        self[ 'viewBox' ] = viewBox
        self.param = ( width, height, viewBox, rand )
    #--------------------------------------------------------------------------
    def header( self ):
        f = floatfmt
        ( wt, ht, ( x, y, w, h ) ) = self.param[ :3 ]
        ( width, height, x, y, w, h ) = [ f%r for r in ( wt, ht, x, y, w, h ) ]
        return '''<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="%spx" height="%spx"
     viewBox="%s %s %s %s">\n'''%(
            width, height, x, y, w, h )
    #--------------------------------------------------------------------------
    def rand( self ):
        'fuegt rand der breite b hinzu'
        ( ( x, y, w, h ), b ) = self.param[ 2: ]
        g = Group( oid='border', fill='white' )
        g.addObjs( (
            Rect( oid='left', x=x-b, y=y, width=2*b, height=h ),
            Rect( oid='right', x=x+w-b, y=y, width=2*b, height=h ),
            Rect( oid='upper', x=x, y=y-b, width=w, height=2*b ),
            Rect( oid='lower', x=x, y=y+h-b, width=w, height=2*b )
            ) )
        return g
    #--------------------------------------------------------------------------
    def pre( self ):
        'preliminaries'
        ( defs, geom ) = ( Defs( oid='preliminaries' ),[] )
        ok = 0
        for obj in self.objs:
            if isinstance( obj, Line ) or isinstance( obj, Container ):
                obj[ 'viewBox' ] = self.param[ 2 ]
            if isinstance( obj, Defs ):
                ok = 1
                defs.addObjs( obj.objs )
            else:
                geom.append( obj )
        if ok:
            self.objs = [ defs ] + geom
        else:
            self.objs = geom
        if self.param[ -1 ] > 0:
            self.objs.append( self.rand() )
    #--------------------------------------------------------------------------
    def write( self, name ):
        'schreibt svg-datei'
        self.pre()
        txt = self.header()
        txt += Group.svgCode( self )
        txt += '\n</svg>\n'
        fid = open( name, 'w' )
        fid.write( txt )
        fid.close()
        print( '%s geschrieben'%fid.name )
    #--------------------------------------------------------------------------
#==============================================================================
class SvgFlip( Svg ):
    """schreibt svg yflip=ymax+ymin im Anschauungsraum
    masseinheiten: pt=1.5*px, mm=3.78*px
    matrix( a,b,c,d,e,f )             Bild               Anschauungsraum
    x' = a*x + c*y + e                x0       x0+B      
    y' = b*x + d*y + f              y0+----------+   y0+H+----------+
    viewBox=[x0,y0,B,H]               |  (x,y)   |       |  (x',y') |    
    (x',y')(x0,y0+H) = (0,0)          |          |       |          |    
    (x',y')(x0+B,y0+H) = (B,H)    y0+H+----------+     y0+----------+
                                                        x0       x0+B
    """
    #--------------------------------------------------------------------------
    def __init__( self, width=400, height=400, viewBox=[-200,-200,400,400],
                  fmt='%1.0f', matrix=[], yflip=0 ):
        Svg.__init__( self, width, height, viewBox, fmt )
        self[ 'matrix' ] = self.matrix = ( 1, 0, 0, -1, 0, yflip )
    #--------------------------------------------------------------------------
#==============================================================================
def bench():
    svg = Svg( width=400, height=400, viewBox=[-200,-200,400,400] )
    g = Group( oid='leuchtturm', translate=(-9,-18), scale=(0.02, 0.02 ) )
    g.addObjs( [
        Polygon( oid='boden',
                 points=( (675,675), (450,900), (1350,900), (1125,675) ),
                 fill="#a14000", stroke="black", strokewidth=8 ),
        Rect( oid='oben',
              x=675, y=0, width=450, height=225, fill="white",
              stroke="black", strokewidth=8 ),
        Rect( oid='mitte',
              x=675, y=225, width=450, height=225,
              stroke="black", strokewidth=8 ),
        Rect( oid='unten',
              x=675, y=450, width=450, height=225, fill="white",
              stroke="black", strokewidth=8 ),
        Polygon( oid='absatz',
                 points=( (675,0), (1125,0), (1035,-90), (765,-90) ),
                 fill="black", stroke="black", strokewidth=8 ),
        Rect( oid='lampe',
              x=765, y=-315, width=270, height=225, fill="yellow",
              stroke="black", strokewidth=8 ),
        Polygon( oid='dach',
                 points=( (765,-315), (1035,-315), (900,-450) ),
                 fill="black", stroke="black", strokewidth=8 )
        ] )
    d = Defs()
    d.addObj( g )
    svg.addObj( d )
    svg.addObj( Use( x=0, y=-170, href='leuchtturm', fill='red' ) )
    svg.addObj( Use( x=20, y=-170, href='leuchtturm', fill='blue' ) )
    svg.addObj( Use( x=40, y=-170, href='leuchtturm', fill='green2' ) )
    svg.addObj( Polyline( stroke='black', points=( ( -200,0 ), ( 200,0 ) ) ) )
    svg.addObj( Polyline( stroke='black',
                          points=( ( -200,-170 ), ( 200,-170 ) ) ) )
    svg.addObj( Polyline( stroke='black', points=( ( 0,-200 ), ( 0,200 ) ) ) )
    svg.addObj( Polyline( stroke='black', points=( ( 20,-200 ), ( 20,200 ) ) ))
    a = Stop( offset=0, color='#red', opacity=1 )
    b = Stop( offset=100, color='blue', opacity=1 )
    svg.addObj( LinearGradient( # Primitive
        oid='wasser',
        # LinearGradient
        x1=0, y1=0, x2=0, y2=100, stops=( a,b ) ) )
    svg.addObj( Path( stroke='black',
                      d='M 4 0 L 0 5 L 16 0 L 0 -5 z',
                      strokewidth='',
                      scale=(10,10) ) )
    m = Marker( oid='pfeil',
                refX=16, refY=0, markerWidth=20, markerHeight=20,
                orient='auto', viewBox=[ 0.0, -5.0, 16.0, 10.0 ] )
    m.addObj( Path( stroke='black', d='M 4 0 L 0 5 L 16 0 L 0 -5 L 4 0 Z',
                    url='wasser', strokewidth=1 ) )
    svg.addObj( m )
    svg.addObj( Rect( # Primitive
        fillopacity=0.7,
        # Geometry
        url='wasser', stroke='black', strokewidth=8, dasharray=(8,16),
        # Rect
        x=-75, y=70, width=150, height=100 ) )
    svg.addObj( Rect( # Primitive
        fillopacity=0.7,
        translate=(-50,0), rotate=40, scale=(0.5,1),
        # Geometry
        fill='blue', stroke='black', strokewidth=8, dasharray=(8,16),
        # Rect
        x=0, y=0, width=150, height=100 ) )
    svg.addObj( Circle( # Primitive
        fillopacity=0.7, rotate=-40, scale=(0.5,1),
        # Geometry
        fill='yellow', stroke='black', strokewidth=8, dasharray=(8,16),
        # Circle
        cx=0, cy=0, r=150 ) )
    svg.addObj( Ellipse( # Primitive
        oid='name', fillopacity=0.7, rotate=40,
        # Geometry
        fill='gold', stroke='black', strokewidth=8, dasharray=(8,16),
        # Ellipse
        cx=0, cy=0, rx=150, ry=75 ) )
    svg.addObj( Polygon( points=[ ( 0,0 ), ( 30, 30 ), ( 60, 0 ) ],
                         translate=(-100,-110), stroke='green2' ) )
    svg.addObj( Polyline( points=[ ( 0,0 ), ( 30, 30 ), ( 60, 0 ) ],
                          translate=(100,-100), stroke='red',
                          markerend='pfeil' ) )
    svg.addObj( Oval( points=[ ( 0,0 ), ( 30, 30 ), ( 60, 0 ) ],
                      translate=(0,-100), stroke='blue',
                      markerend='pfeil' ) )
    t = Text( oid='hallo', x=0, y=0, label='Hallo', size=50, url='wasser' )
    svg.addObj( t )
    g = Group( translate=(0,-100) )
    g.addObj( t )
    svg.addObj( g )
    svg.addObj( Use( oid='usit', x=0, y=-300, href='hallo' ) )
    svg.addObj( Image( oid='bulb_on.png',
       name='/home/austausch/doku/freizeit/wikipedia/math/baseler/bulb_on.png',
       x=0, y=-100, width=100, height=100,
       rotate=10, opacity=0.5 ) )
    svg.addObj( Winkel( oid='winkel', fillopacity=0.3,
                        stroke="green", strokewidth=4, fill="#00d000",
                        r=100, points=[ (0,0), (5,0), (0,5) ],
                        text=Text( oid='name', fill='black', size=50, label='a'
                                   ) ) )
    svg.write( 'tmp.svg' )
#==============================================================================
if __name__ == '__main__':
    bench()