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 #-- skewX --
59 if result.group(1)=="skewX":
60 a=float(result.group(2))*math.pi/180
61 matrix=[[1,math.tan(a),0],[0,1,0]]
62 #-- skewY --
63 if result.group(1)=="skewY":
64 a=float(result.group(2))*math.pi/180
65 matrix=[[1,0,0],[math.tan(a),1,0]]
66 #-- matrix --
67 if result.group(1)=="matrix":
68 a11,a21,a12,a22,v1,v2=result.group(2).replace(',',' ').split()
69 matrix=[[float(a11),float(a12),float(v1)],[float(a21),float(a22),float(v2)]]
71 matrix=composeTransform(mat,matrix)
72 if result.end()<len(stransf):
73 return(parseTransform(stransf[result.end():],matrix))
74 else:
75 return matrix
77 def formatTransform(mat):
78 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]))
80 def composeTransform(M1,M2):
81 a11=M1[0][0]*M2[0][0]+M1[0][1]*M2[1][0]
82 a12=M1[0][0]*M2[0][1]+M1[0][1]*M2[1][1]
83 a21=M1[1][0]*M2[0][0]+M1[1][1]*M2[1][0]
84 a22=M1[1][0]*M2[0][1]+M1[1][1]*M2[1][1]
86 v1=M1[0][0]*M2[0][2]+M1[0][1]*M2[1][2]+M1[0][2]
87 v2=M1[1][0]*M2[0][2]+M1[1][1]*M2[1][2]+M1[1][2]
88 return [[a11,a12,v1],[a21,a22,v2]]
90 def applyTransformToNode(mat,node):
91 m=parseTransform(node.get("transform"))
92 newtransf=formatTransform(composeTransform(mat,m))
93 node.set("transform", newtransf)
95 def applyTransformToPoint(mat,pt):
96 x=mat[0][0]*pt[0]+mat[0][1]*pt[1]+mat[0][2]
97 y=mat[1][0]*pt[0]+mat[1][1]*pt[1]+mat[1][2]
98 pt[0]=x
99 pt[1]=y
101 def applyTransformToPath(mat,path):
102 for comp in path:
103 for ctl in comp:
104 for pt in ctl:
105 applyTransformToPoint(mat,pt)
107 def fuseTransform(node):
108 if node.get('d')==None:
109 #FIX ME: how do you raise errors?
110 raise AssertionError, 'can not fuse "transform" of elements that have no "d" attribute'
111 t = node.get("transform")
112 if t == None:
113 return
114 m = parseTransform(t)
115 d = node.get('d')
116 p = cubicsuperpath.parsePath(d)
117 applyTransformToPath(m,p)
118 node.set('d', cubicsuperpath.formatPath(p))
119 del node.attrib["transform"]
121 ####################################################################
122 ##-- Some functions to compute a rough bbox of a given list of objects.
123 ##-- this should be shipped out in an separate file...
125 def boxunion(b1,b2):
126 if b1 is None:
127 return b2
128 elif b2 is None:
129 return b1
130 else:
131 return((min(b1[0],b2[0]),max(b1[1],b2[1]),min(b1[2],b2[2]),max(b1[3],b2[3])))
133 def roughBBox(path):
134 xmin,xMax,ymin,yMax = path[0][0][0][0],path[0][0][0][0],path[0][0][0][1],path[0][0][0][1]
135 for pathcomp in path:
136 for ctl in pathcomp:
137 for pt in ctl:
138 xmin = min(xmin,pt[0])
139 xMax = max(xMax,pt[0])
140 ymin = min(ymin,pt[1])
141 yMax = max(yMax,pt[1])
142 return xmin,xMax,ymin,yMax
144 def computeBBox(aList,mat=[[1,0,0],[0,1,0]]):
145 bbox=None
146 for node in aList:
147 m = parseTransform(node.get('transform'))
148 m = composeTransform(mat,m)
149 #TODO: text not supported!
150 if node.get("d"):
151 d = node.get('d')
152 p = cubicsuperpath.parsePath(d)
153 applyTransformToPath(m,p)
154 bbox=boxunion(roughBBox(p),bbox)
156 elif node.tag == inkex.addNS('rect','svg') or node.tag=='rect':
157 w = float(node.get('width'))/2.
158 h = float(node.get('height'))/2.
159 x = float(node.get('x'))
160 y = float(node.get('y'))
161 C = [x + w , y + h ]
162 applyTransformToPoint(mat,C)
163 xmin = C[0] - abs(m[0][0]) * w - abs(m[0][1]) * h
164 xmax = C[0] + abs(m[0][0]) * w + abs(m[0][1]) * h
165 ymin = C[1] - abs(m[1][0]) * w - abs(m[1][1]) * h
166 ymax = C[1] + abs(m[1][0]) * w + abs(m[1][1]) * h
167 bbox = xmin,xmax,ymin,ymax
169 elif node.tag == inkex.addNS('use','svg') or node.tag=='use':
170 refid=node.get(inkex.addNS('href','xlink'))
171 path = '//*[@id="%s"]' % refid[1:]
172 refnode = node.xpath(path)
173 bbox=boxunion(computeBBox(refnode,m),bbox)
175 bbox=boxunion(computeBBox(node,m),bbox)
176 return bbox
179 # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99