4 # Copyright (C) 2002-2003 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002-2003 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 import types
, re
, math
, string
, sys
25 import bbox
, path
, unit
, trafo
, helper
28 class BoxCrossError(Exception): pass
32 def __init__(self
, corners
=None, center
=None):
33 self
.corners
= corners
35 if self
.center
is None:
38 def _ensurecenter(self
):
39 if self
.center
is None:
41 for corn
in self
.corners
:
42 self
.center
= [self
.center
[0] + corn
[0], self
.center
[1] + corn
[1]]
43 self
.center
= [self
.center
[0]/len(self
.corners
), self
.center
[1]/len(self
.corners
)]
45 def path(self
, centerradius
=None, bezierradius
=None, beziersoftness
=1):
47 if centerradius
is not None and self
.center
is not None:
48 r
= unit
.topt(unit
.length(centerradius
, default_type
="v"))
49 pathels
.append(path
._arc
(self
.center
[0], self
.center
[1], r
, 0, 360))
50 pathels
.append(path
.closepath())
51 if bezierradius
is None:
52 pathels
.append(path
._moveto
(self
.corners
[0][0], self
.corners
[0][1]))
53 for x
, y
in self
.corners
[1:]:
54 pathels
.append(path
._lineto
(x
, y
))
55 pathels
.append(path
.closepath())
57 # curved box plotting by Michael Schindler
59 if helper
.issequence(beziersoftness
):
60 if not (len(beziersoftness
) == l
): raise ValueError
62 beziersoftness
= [float(beziersoftness
)]*l
63 if helper
.issequence(bezierradius
):
64 r
= list(bezierradius
)
65 if len(bezierradius
) == l
:
66 for oner
, i
in zip(r
, range(l
)):
67 if helper
.issequence(oner
):
69 r
[i
] = [unit
.topt(oner
[0]), unit
.topt(oner
[1])]
70 else: raise ValueError
72 r
[i
] = [unit
.topt(oner
)]*2
73 else: raise ValueError
75 r
= [[unit
.topt(bezierradius
)]*2]*l
79 n
= math
.sqrt(v
[0] * v
[0] + v
[1] * v
[1])
80 return v
[0] / n
, v
[1] / n
81 d1
= normed(self
.corners
[(i
- 1 + l
) % l
][0] - c
[0],
82 self
.corners
[(i
- 1 + l
) % l
][1] - c
[1])
83 d2
= normed(self
.corners
[(i
+ 1 + l
) % l
][0] - c
[0],
84 self
.corners
[(i
+ 1 + l
) % l
][1] - c
[1])
85 dc
= normed(d1
[0] + d2
[0], d1
[1] + d2
[1])
86 f
= 0.3192 * beziersoftness
[i
]
87 g
= (15.0 * f
+ math
.sqrt(-15.0*f
*f
+ 24.0*f
))/12.0
88 f1
= c
[0] + f
* d1
[0] * r
[i
][0], c
[1] + f
* d1
[1] * r
[i
][0]
89 f2
= c
[0] + f
* d2
[0] * r
[i
][1], c
[1] + f
* d2
[1] * r
[i
][1]
90 g1
= c
[0] + g
* d1
[0] * r
[i
][0], c
[1] + g
* d1
[1] * r
[i
][0]
91 g2
= c
[0] + g
* d2
[0] * r
[i
][1], c
[1] + g
* d2
[1] * r
[i
][1]
92 d1
= c
[0] + d1
[0] * r
[i
][0], c
[1] + d1
[1] * r
[i
][0]
93 d2
= c
[0] + d2
[0] * r
[i
][1], c
[1] + d2
[1] * r
[i
][1]
94 e
= 0.5 * (f1
[0] + f2
[0]), 0.5 * (f1
[1] + f2
[1])
96 pathels
.append(path
._lineto
(*d1
))
98 pathels
.append(path
._moveto
(*d1
))
99 pathels
.append(path
._curveto
(*(g1
+ f1
+ e
)))
100 pathels
.append(path
._curveto
(*(f2
+ g2
+ d2
)))
101 pathels
.append(path
.closepath())
102 return path
.path(*pathels
)
104 def transform(self
, *trafos
):
106 if self
.center
is not None:
107 self
.center
= trafo
._apply
(*self
.center
)
108 self
.corners
= [trafo
._apply
(*point
) for point
in self
.corners
]
110 def reltransform(self
, *trafos
):
111 if self
.center
is not None:
112 trafos
= ([trafo
._translate
(-self
.center
[0], -self
.center
[1])] +
114 [trafo
._translate
(self
.center
[0], self
.center
[1])])
115 self
.transform(*trafos
)
117 def successivepointnumbers(self
):
118 return [i
and (i
- 1, i
) or (len(self
.corners
) - 1, 0) for i
in range(len(self
.corners
))]
120 def successivepoints(self
):
121 return [(self
.corners
[i
], self
.corners
[j
]) for i
, j
in self
.successivepointnumbers()]
123 def _circlealignlinevector(self
, a
, dx
, dy
, ex
, ey
, fx
, fy
, epsilon
=1e-10):
125 gx
, gy
= ex
- fx
, ey
- fy
# direction vector
126 if gx
*gx
+ gy
*gy
< epsilon
: # zero line length
127 return None # no solution -> return None
128 rsplit
= (dx
*gx
+ dy
*gy
) * 1.0 / (gx
*gx
+ gy
*gy
)
129 bx
, by
= dx
- gx
* rsplit
, dy
- gy
* rsplit
130 if bx
*bx
+ by
*by
< epsilon
: # zero projection
131 return None # no solution -> return None
132 if bx
*gy
- by
*gx
< 0: # half space
133 return None # no solution -> return None
134 sfactor
= math
.sqrt((dx
*dx
+ dy
*dy
) / (bx
*bx
+ by
*by
))
135 bx
, by
= a
* bx
* sfactor
, a
* by
* sfactor
136 alpha
= ((bx
+cx
-ex
)*dy
- (by
+cy
-ey
)*dx
) * 1.0 / (gy
*dx
- gx
*dy
)
137 if alpha
> 0 - epsilon
and alpha
< 1 + epsilon
:
138 beta
= ((ex
-bx
-cx
)*gy
- (ey
-by
-cy
)*gx
) * 1.0 / (gx
*dy
- gy
*dx
)
139 return beta
*dx
, beta
*dy
# valid solution -> return align tuple
140 # crossing point at the line, but outside a valid range
142 return 0 # crossing point outside e
143 return 1 # crossing point outside f
145 def _linealignlinevector(self
, a
, dx
, dy
, ex
, ey
, fx
, fy
, epsilon
=1e-10):
147 gx
, gy
= ex
- fx
, ey
- fy
# direction vector
148 if gx
*gx
+ gy
*gy
< epsilon
: # zero line length
149 return None # no solution -> return None
150 if gy
*dx
- gx
*dy
< -epsilon
: # half space
151 return None # no solution -> return None
152 if dx
*gx
+ dy
*gy
> epsilon
or dx
*gx
+ dy
*gy
< -epsilon
:
153 if dx
*gx
+ dy
*gy
< 0: # angle bigger 90 degree
154 return 0 # use point e
155 return 1 # use point f
156 # a and g are othorgonal
157 alpha
= ((a
*dx
+cx
-ex
)*dy
- (a
*dy
+cy
-ey
)*dx
) * 1.0 / (gy
*dx
- gx
*dy
)
158 if alpha
> 0 - epsilon
and alpha
< 1 + epsilon
:
159 beta
= ((ex
-a
*dx
-cx
)*gy
- (ey
-a
*dy
-cy
)*gx
) * 1.0 / (gx
*dy
- gy
*dx
)
160 return beta
*dx
, beta
*dy
# valid solution -> return align tuple
161 # crossing point at the line, but outside a valid range
163 return 0 # crossing point outside e
164 return 1 # crossing point outside f
166 def _circlealignpointvector(self
, a
, dx
, dy
, px
, py
, epsilon
=1e-10):
170 p
= 2 * ((px
-cx
)*dx
+ (py
-cy
)*dy
)
171 q
= ((px
-cx
)*(px
-cx
) + (py
-cy
)*(py
-cy
) - a
*a
)
175 alpha
= - p
/ 2 + math
.sqrt(p
*p
/4 - q
)
177 alpha
= - p
/ 2 - math
.sqrt(p
*p
/4 - q
)
178 return alpha
*dx
, alpha
*dy
180 def _linealignpointvector(self
, a
, dx
, dy
, px
, py
):
182 beta
= (a
*dx
+cx
-px
)*dy
- (a
*dy
+cy
-py
)*dx
183 return a
*dx
- beta
*dy
- px
+ cx
, a
*dy
+ beta
*dx
- py
+ cy
185 def _alignvector(self
, a
, dx
, dy
, alignlinevector
, alignpointvector
):
186 n
= math
.sqrt(dx
* dx
+ dy
* dy
)
187 dx
, dy
= dx
/ n
, dy
/ n
188 linevectors
= map(lambda (p1
, p2
), self
=self
, a
=a
, dx
=dx
, dy
=dy
, alignlinevector
=alignlinevector
:
189 alignlinevector(a
, dx
, dy
, *(p1
+ p2
)), self
.successivepoints())
190 for linevector
in linevectors
:
191 if type(linevector
) is types
.TupleType
:
193 for i
, j
in self
.successivepointnumbers():
194 l1
, l2
= linevectors
[i
], linevectors
[j
]
195 if (l1
is not None or l2
is not None) and (l1
== 1 or l1
is None) and (l2
== 0 or l2
is None):
196 return alignpointvector(a
, dx
, dy
, *self
.successivepoints()[j
][0])
199 def _circlealignvector(self
, a
, dx
, dy
):
200 return self
._alignvector
(a
, dx
, dy
, self
._circlealignlinevector
, self
._circlealignpointvector
)
202 def _linealignvector(self
, a
, dx
, dy
):
203 return self
._alignvector
(a
, dx
, dy
, self
._linealignlinevector
, self
._linealignpointvector
)
205 def circlealignvector(self
, a
, dx
, dy
):
206 return map(unit
.t_pt
, self
._circlealignvector
(unit
.topt(a
), dx
, dy
))
208 def linealignvector(self
, a
, dx
, dy
):
209 return map(unit
.t_pt
, self
._linealignvector
(unit
.topt(a
), dx
, dy
))
211 def _circlealign(self
, *args
):
212 self
.transform(trafo
._translate
(*self
._circlealignvector
(*args
)))
215 def _linealign(self
, *args
):
216 self
.transform(trafo
._translate
(*self
._linealignvector
(*args
)))
219 def circlealign(self
, *args
):
220 self
.transform(trafo
.translate(*self
.circlealignvector(*args
)))
223 def linealign(self
, *args
):
224 self
.transform(trafo
.translate(*self
.linealignvector(*args
)))
227 def _extent(self
, dx
, dy
):
228 n
= math
.sqrt(dx
* dx
+ dy
* dy
)
229 dx
, dy
= dx
/ n
, dy
/ n
230 oldcenter
= self
.center
231 if self
.center
is None:
233 x1
, y1
= self
._linealignvector
(0, dx
, dy
)
234 x2
, y2
= self
._linealignvector
(0, -dx
, -dy
)
235 self
.center
= oldcenter
236 return (x1
-x2
)*dx
+ (y1
-y2
)*dy
238 def extent(self
, dx
, dy
):
239 return unit
.t_pt(self
._extent
(dx
, dy
))
241 def _pointdistance(self
, x
, y
):
243 for p1
, p2
in self
.successivepoints():
244 gx
, gy
= p2
[0] - p1
[0], p2
[1] - p1
[1]
245 if gx
* gx
+ gy
* gy
< 1e-10:
246 dx
, dy
= p1
[0] - x
, p1
[1] - y
248 a
= (gx
* (x
- p1
[0]) + gy
* (y
- p1
[1])) / (gx
* gx
+ gy
* gy
)
250 dx
, dy
= p1
[0] - x
, p1
[1] - y
252 dx
, dy
= p2
[0] - x
, p2
[1] - y
254 dx
, dy
= x
- p1
[0] - a
* gx
, y
- p1
[1] - a
* gy
255 new
= math
.sqrt(dx
* dx
+ dy
* dy
)
256 if result
is None or new
< result
:
260 def pointdistance(self
, x
, y
):
261 return unit
.t_pt(self
._pointdistance
(unit
.topt(x
), unit
.topt(y
)))
263 def _boxdistance(self
, other
, epsilon
=1e-10):
264 # XXX: boxes crossing and distance calculation is O(N^2)
265 for p1
, p2
in self
.successivepoints():
266 for p3
, p4
in other
.successivepoints():
267 a
= (p4
[1] - p3
[1]) * (p3
[0] - p1
[0]) - (p4
[0] - p3
[0]) * (p3
[1] - p1
[1])
268 b
= (p2
[1] - p1
[1]) * (p3
[0] - p1
[0]) - (p2
[0] - p1
[0]) * (p3
[1] - p1
[1])
269 c
= (p2
[0] - p1
[0]) * (p4
[1] - p3
[1]) - (p2
[1] - p1
[1]) * (p4
[0] - p3
[0])
270 if (abs(c
) > 1e-10 and
271 a
/ c
> -epsilon
and a
/ c
< 1 + epsilon
and
272 b
/ c
> -epsilon
and b
/ c
< 1 + epsilon
):
275 for x
, y
in other
.corners
:
276 new
= self
._pointdistance
(x
, y
)
277 if result
is None or new
< result
:
279 for x
, y
in self
.corners
:
280 new
= other
._pointdistance
(x
, y
)
281 if result
is None or new
< result
:
285 def boxdistance(self
, other
):
286 return unit
.t_pt(self
._boxdistance
(other
))
289 return bbox
._bbox
(min([x
[0] for x
in self
.corners
]),
290 min([x
[1] for x
in self
.corners
]),
291 max([x
[0] for x
in self
.corners
]),
292 max([x
[1] for x
in self
.corners
]))
295 def _genericalignequal(method
, polygons
, a
, dx
, dy
):
298 v
= method(p
, a
, dx
, dy
)
299 if vec
is None or vec
[0] * dx
+ vec
[1] * dy
< v
[0] * dx
+ v
[1] * dy
:
302 p
.transform(trafo
._translate
(*vec
))
305 def _circlealignequal(polygons
, *args
):
306 _genericalignequal(_polygon
._circlealignvector
, polygons
, *args
)
308 def _linealignequal(polygons
, *args
):
309 _genericalignequal(_polygon
._linealignvector
, polygons
, *args
)
311 def circlealignequal(polygons
, a
, *args
):
312 _circlealignequal(polygons
, unit
.topt(a
), *args
)
314 def linealignequal(polygons
, a
, *args
):
315 _linealignequal(polygons
, unit
.topt(a
), *args
)
318 def _tile(polygons
, a
, dx
, dy
):
319 maxextent
= polygons
[0]._extent
(dx
, dy
)
320 for p
in polygons
[1:]:
321 extent
= p
._extent
(dx
, dy
)
322 if extent
> maxextent
:
326 p
.transform(trafo
._translate
(d
*dx
, d
*dy
))
330 def tile(polygons
, a
, dx
, dy
):
331 _tile(polygons
, unit
.topt(a
), dx
, dy
)
334 class polygon(_polygon
):
336 def __init__(self
, corners
=None, center
=None, **args
):
337 corners
= [[unit
.topt(x
) for x
in corner
] for corner
in corners
]
338 if center
is not None:
339 center
= map(unit
.topt
, center
)
340 _polygon
.__init
__(self
, corners
=corners
, center
=center
, **args
)
343 class _rect(_polygon
):
345 def __init__(self
, x
, y
, width
, height
, relcenter
=(0, 0), abscenter
=(0, 0),
346 corners
=helper
.nodefault
, center
=helper
.nodefault
, **args
):
347 if corners
!= helper
.nodefault
or center
!= helper
.nodefault
:
349 _polygon
.__init
__(self
, corners
=((x
, y
),
351 (x
+ width
, y
+ height
),
353 center
=(x
+ relcenter
[0] * width
+ abscenter
[0],
354 y
+ relcenter
[1] * height
+ abscenter
[1]),
360 def __init__(self
, x
, y
, width
, height
, relcenter
=(0, 0), abscenter
=(0, 0), **args
):
361 _rect
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), unit
.topt(width
), unit
.topt(height
),
362 relcenter
=relcenter
, abscenter
=(unit
.topt(abscenter
[0]), unit
.topt(abscenter
[1])), **args
)