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