Code

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