Code

- Remove some old code which snapped the rotation center to the bbox, and which disca...
[inkscape.git] / share / extensions / simpletransform.py
1 #!/usr/bin/env python
2 '''
3 Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 barraud@math.univ-lille1.fr
20 This code defines several functions to make handling of transform
21 attribute easier.
22 '''
23 import inkex, cubicsuperpath, bezmisc, simplestyle
24 import copy, math, re
26 def parseTransform(transf,mat=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]):
27     if transf=="" or transf==None:
28         return(mat)
29     stransf = transf.strip()
30     result=re.match("(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)\s*,?",stransf)
31 #-- translate --
32     if result.group(1)=="translate":
33         args=result.group(2).replace(',',' ').split()
34         dx=float(args[0])
35         if len(args)==1:
36             dy=0.0
37         else:
38             dy=float(args[1])
39         matrix=[[1,0,dx],[0,1,dy]]
40 #-- scale --
41     if result.group(1)=="scale":
42         args=result.group(2).replace(',',' ').split()
43         sx=float(args[0])
44         if len(args)==1:
45             sy=sx
46         else:
47             sy=float(args[1])
48         matrix=[[sx,0,0],[0,sy,0]]
49 #-- rotate --
50     if result.group(1)=="rotate":
51         args=result.group(2).replace(',',' ').split()
52         a=float(args[0])*math.pi/180
53         if len(args)==1:
54             cx,cy=(0.0,0.0)
55         else:
56             cx,cy=map(float,args[1:])
57         matrix=[[math.cos(a),-math.sin(a),cx],[math.sin(a),math.cos(a),cy]]
58         matrix=composeTransform(matrix,[[1,0,-cx],[0,1,-cy]])
59 #-- skewX --
60     if result.group(1)=="skewX":
61         a=float(result.group(2))*math.pi/180
62         matrix=[[1,math.tan(a),0],[0,1,0]]
63 #-- skewY --
64     if result.group(1)=="skewY":
65         a=float(result.group(2))*math.pi/180
66         matrix=[[1,0,0],[math.tan(a),1,0]]
67 #-- matrix --
68     if result.group(1)=="matrix":
69         a11,a21,a12,a22,v1,v2=result.group(2).replace(',',' ').split()
70         matrix=[[float(a11),float(a12),float(v1)], [float(a21),float(a22),float(v2)]]
72     matrix=composeTransform(mat,matrix)
73     if result.end() < len(stransf):
74         return(parseTransform(stransf[result.end():], matrix))
75     else:
76         return matrix
78 def formatTransform(mat):
79     return ("matrix(%f,%f,%f,%f,%f,%f)" % (mat[0][0], mat[1][0], mat[0][1], mat[1][1], mat[0][2], mat[1][2]))
81 def composeTransform(M1,M2):
82     a11 = M1[0][0]*M2[0][0] + M1[0][1]*M2[1][0]
83     a12 = M1[0][0]*M2[0][1] + M1[0][1]*M2[1][1]
84     a21 = M1[1][0]*M2[0][0] + M1[1][1]*M2[1][0]
85     a22 = M1[1][0]*M2[0][1] + M1[1][1]*M2[1][1]
87     v1 = M1[0][0]*M2[0][2] + M1[0][1]*M2[1][2] + M1[0][2]
88     v2 = M1[1][0]*M2[0][2] + M1[1][1]*M2[1][2] + M1[1][2]
89     return [[a11,a12,v1],[a21,a22,v2]]
91 def applyTransformToNode(mat,node):
92     m=parseTransform(node.get("transform"))
93     newtransf=formatTransform(composeTransform(mat,m))
94     node.set("transform", newtransf)
96 def applyTransformToPoint(mat,pt):
97     x = mat[0][0]*pt[0] + mat[0][1]*pt[1] + mat[0][2]
98     y = mat[1][0]*pt[0] + mat[1][1]*pt[1] + mat[1][2]
99     pt[0]=x
100     pt[1]=y
102 def applyTransformToPath(mat,path):
103     for comp in path:
104         for ctl in comp:
105             for pt in ctl:
106                 applyTransformToPoint(mat,pt)
108 def fuseTransform(node):
109     if node.get('d')==None:
110         #FIXME: how do you raise errors?
111         raise AssertionError, 'can not fuse "transform" of elements that have no "d" attribute'
112     t = node.get("transform")
113     if t == None:
114         return
115     m = parseTransform(t)
116     d = node.get('d')
117     p = cubicsuperpath.parsePath(d)
118     applyTransformToPath(m,p)
119     node.set('d', cubicsuperpath.formatPath(p))
120     del node.attrib["transform"]
122 ####################################################################
123 ##-- Some functions to compute a rough bbox of a given list of objects.
124 ##-- this should be shipped out in an separate file...
126 def boxunion(b1,b2):
127     if b1 is None:
128         return b2
129     elif b2 is None:
130         return b1    
131     else:
132         return((min(b1[0],b2[0]), max(b1[1],b2[1]), min(b1[2],b2[2]), max(b1[3],b2[3])))
134 def roughBBox(path):
135     xmin,xMax,ymin,yMax = path[0][0][0][0],path[0][0][0][0],path[0][0][0][1],path[0][0][0][1]
136     for pathcomp in path:
137         for ctl in pathcomp:
138             for pt in ctl:
139                 xmin = min(xmin,pt[0])
140                 xMax = max(xMax,pt[0])
141                 ymin = min(ymin,pt[1])
142                 yMax = max(yMax,pt[1])
143     return xmin,xMax,ymin,yMax
145 def computeBBox(aList,mat=[[1,0,0],[0,1,0]]):
146     bbox=None
147     for node in aList:
148         m = parseTransform(node.get('transform'))
149         m = composeTransform(mat,m)
150         #TODO: text not supported!
151         d = None
152         if node.get("d"):
153             d = node.get('d')
154         elif node.get('points'):
155             d = 'M' + node.get('points')
156         elif node.tag in [ inkex.addNS('rect','svg'), 'rect' ]:
157             d = 'M' + node.get('x', '0') + ',' + node.get('y', '0') + \
158                 'h' + node.get('width') + 'v' + node.get('height') + \
159                 'h-' + node.get('width')
160         elif node.tag in [ inkex.addNS('line','svg'), 'line' ]:
161             d = 'M' + node.get('x1') + ',' + node.get('y1') + \
162                 ' ' + node.get('x2') + ',' + node.get('y2')
163         elif node.tag in [ inkex.addNS('circle','svg'), 'circle', \
164                             inkex.addNS('ellipse','svg'), 'ellipse' ]:
165             rx = node.get('r')
166             if rx is not None:
167                 ry = rx
168             else:
169                 rx = node.get('rx')
170                 ry = node.get('ry')
171             cx = float(node.get('cx', '0'))
172             cy = float(node.get('cy', '0'))
173             x1 = cx - float(rx)
174             x2 = cx + float(rx)
175             d = 'M %f %f ' % (x1, cy) + \
176                 'A' + rx + ',' + ry + ' 0 1 0 %f,%f' % (x2, cy) + \
177                 'A' + rx + ',' + ry + ' 0 1 0 %f,%f' % (x1, cy)
178  
179         if d is not None:
180             p = cubicsuperpath.parsePath(d)
181             applyTransformToPath(m,p)
182             bbox=boxunion(roughBBox(p),bbox)
184         elif node.tag == inkex.addNS('use','svg') or node.tag=='use':
185             refid=node.get(inkex.addNS('href','xlink'))
186             path = '//*[@id="%s"]' % refid[1:]
187             refnode = node.xpath(path)
188             bbox=boxunion(computeBBox(refnode,m),bbox)
189             
190         bbox=boxunion(computeBBox(node,m),bbox)
191     return bbox
194 # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99