#!/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()