2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2003 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002-2003 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 import bbox
, path
, unit
, trafo
, helper
29 class BoxCrossError(Exception): pass
33 def __init__(self
, corners
=None, center
=None):
34 self
.corners
= corners
36 if self
.center
is None:
39 def _ensurecenter(self
):
40 if self
.center
is None:
42 for corn
in self
.corners
:
43 self
.center
= [self
.center
[0] + corn
[0], self
.center
[1] + corn
[1]]
44 self
.center
= [self
.center
[0]/len(self
.corners
), self
.center
[1]/len(self
.corners
)]
46 def path(self
, centerradius
=None, bezierradius
=None, beziersoftness
=1):
48 if centerradius
is not None and self
.center
is not None:
49 r
= unit
.topt(unit
.length(centerradius
, default_type
="v"))
50 pathels
.append(path
._arc
(self
.center
[0], self
.center
[1], r
, 0, 360))
51 pathels
.append(path
.closepath())
52 if bezierradius
is None:
53 pathels
.append(path
._moveto
(self
.corners
[0][0], self
.corners
[0][1]))
54 for x
, y
in self
.corners
[1:]:
55 pathels
.append(path
._lineto
(x
, y
))
56 pathels
.append(path
.closepath())
58 # curved box plotting by Michael Schindler
60 if helper
.issequence(beziersoftness
):
61 if not (len(beziersoftness
) == l
): raise ValueError
63 beziersoftness
= [float(beziersoftness
)]*l
64 if helper
.issequence(bezierradius
):
65 r
= list(bezierradius
)
66 if len(bezierradius
) == l
:
67 for oner
, i
in zip(r
, range(l
)):
68 if helper
.issequence(oner
):
70 r
[i
] = [unit
.topt(oner
[0]), unit
.topt(oner
[1])]
71 else: raise ValueError
73 r
[i
] = [unit
.topt(oner
)]*2
74 else: raise ValueError
76 r
= [[unit
.topt(bezierradius
)]*2]*l
80 n
= math
.sqrt(v
[0] * v
[0] + v
[1] * v
[1])
81 return v
[0] / n
, v
[1] / n
82 d1
= normed(self
.corners
[(i
- 1 + l
) % l
][0] - c
[0],
83 self
.corners
[(i
- 1 + l
) % l
][1] - c
[1])
84 d2
= normed(self
.corners
[(i
+ 1 + l
) % l
][0] - c
[0],
85 self
.corners
[(i
+ 1 + l
) % l
][1] - c
[1])
86 dc
= normed(d1
[0] + d2
[0], d1
[1] + d2
[1])
87 f
= 0.3192 * beziersoftness
[i
]
88 g
= (15.0 * f
+ math
.sqrt(-15.0*f
*f
+ 24.0*f
))/12.0
89 f1
= c
[0] + f
* d1
[0] * r
[i
][0], c
[1] + f
* d1
[1] * r
[i
][0]
90 f2
= c
[0] + f
* d2
[0] * r
[i
][1], c
[1] + f
* d2
[1] * r
[i
][1]
91 g1
= c
[0] + g
* d1
[0] * r
[i
][0], c
[1] + g
* d1
[1] * r
[i
][0]
92 g2
= c
[0] + g
* d2
[0] * r
[i
][1], c
[1] + g
* d2
[1] * r
[i
][1]
93 d1
= c
[0] + d1
[0] * r
[i
][0], c
[1] + d1
[1] * r
[i
][0]
94 d2
= c
[0] + d2
[0] * r
[i
][1], c
[1] + d2
[1] * r
[i
][1]
95 e
= 0.5 * (f1
[0] + f2
[0]), 0.5 * (f1
[1] + f2
[1])
97 pathels
.append(path
._lineto
(*d1
))
99 pathels
.append(path
._moveto
(*d1
))
100 pathels
.append(path
._curveto
(*(g1
+ f1
+ e
)))
101 pathels
.append(path
._curveto
(*(f2
+ g2
+ d2
)))
102 pathels
.append(path
.closepath())
103 return path
.path(*pathels
)
105 def transform(self
, *trafos
):
107 if self
.center
is not None:
108 self
.center
= trafo
._apply
(*self
.center
)
109 self
.corners
= [trafo
._apply
(*point
) for point
in self
.corners
]
111 def reltransform(self
, *trafos
):
112 if self
.center
is not None:
113 trafos
= ([trafo
._translate
(-self
.center
[0], -self
.center
[1])] +
115 [trafo
._translate
(self
.center
[0], self
.center
[1])])
116 self
.transform(*trafos
)
118 def successivepointnumbers(self
):
119 return [i
and (i
- 1, i
) or (len(self
.corners
) - 1, 0) for i
in range(len(self
.corners
))]
121 def successivepoints(self
):
122 return [(self
.corners
[i
], self
.corners
[j
]) for i
, j
in self
.successivepointnumbers()]
124 def _circlealignlinevector(self
, a
, dx
, dy
, ex
, ey
, fx
, fy
, epsilon
=1e-10):
126 gx
, gy
= ex
- fx
, ey
- fy
# direction vector
127 if gx
*gx
+ gy
*gy
< epsilon
: # zero line length
128 return None # no solution -> return None
129 rsplit
= (dx
*gx
+ dy
*gy
) * 1.0 / (gx
*gx
+ gy
*gy
)
130 bx
, by
= dx
- gx
* rsplit
, dy
- gy
* rsplit
131 if bx
*bx
+ by
*by
< epsilon
: # zero projection
132 return None # no solution -> return None
133 if bx
*gy
- by
*gx
< 0: # half space
134 return None # no solution -> return None
135 sfactor
= math
.sqrt((dx
*dx
+ dy
*dy
) / (bx
*bx
+ by
*by
))
136 bx
, by
= a
* bx
* sfactor
, a
* by
* sfactor
137 alpha
= ((bx
+cx
-ex
)*dy
- (by
+cy
-ey
)*dx
) * 1.0 / (gy
*dx
- gx
*dy
)
138 if alpha
> 0 - epsilon
and alpha
< 1 + epsilon
:
139 beta
= ((ex
-bx
-cx
)*gy
- (ey
-by
-cy
)*gx
) * 1.0 / (gx
*dy
- gy
*dx
)
140 return beta
*dx
, beta
*dy
# valid solution -> return align tuple
141 # crossing point at the line, but outside a valid range
143 return 0 # crossing point outside e
144 return 1 # crossing point outside f
146 def _linealignlinevector(self
, a
, dx
, dy
, ex
, ey
, fx
, fy
, epsilon
=1e-10):
148 gx
, gy
= ex
- fx
, ey
- fy
# direction vector
149 if gx
*gx
+ gy
*gy
< epsilon
: # zero line length
150 return None # no solution -> return None
151 if gy
*dx
- gx
*dy
< -epsilon
: # half space
152 return None # no solution -> return None
153 if dx
*gx
+ dy
*gy
> epsilon
or dx
*gx
+ dy
*gy
< -epsilon
:
154 if dx
*gx
+ dy
*gy
< 0: # angle bigger 90 degree
155 return 0 # use point e
156 return 1 # use point f
157 # a and g are othorgonal
158 alpha
= ((a
*dx
+cx
-ex
)*dy
- (a
*dy
+cy
-ey
)*dx
) * 1.0 / (gy
*dx
- gx
*dy
)
159 if alpha
> 0 - epsilon
and alpha
< 1 + epsilon
:
160 beta
= ((ex
-a
*dx
-cx
)*gy
- (ey
-a
*dy
-cy
)*gx
) * 1.0 / (gx
*dy
- gy
*dx
)
161 return beta
*dx
, beta
*dy
# valid solution -> return align tuple
162 # crossing point at the line, but outside a valid range
164 return 0 # crossing point outside e
165 return 1 # crossing point outside f
167 def _circlealignpointvector(self
, a
, dx
, dy
, px
, py
, epsilon
=1e-10):
171 p
= 2 * ((px
-cx
)*dx
+ (py
-cy
)*dy
)
172 q
= ((px
-cx
)*(px
-cx
) + (py
-cy
)*(py
-cy
) - a
*a
)
176 alpha
= - p
/ 2 + math
.sqrt(p
*p
/4 - q
)
178 alpha
= - p
/ 2 - math
.sqrt(p
*p
/4 - q
)
179 return alpha
*dx
, alpha
*dy
181 def _linealignpointvector(self
, a
, dx
, dy
, px
, py
):
183 beta
= (a
*dx
+cx
-px
)*dy
- (a
*dy
+cy
-py
)*dx
184 return a
*dx
- beta
*dy
- px
+ cx
, a
*dy
+ beta
*dx
- py
+ cy
186 def _alignvector(self
, a
, dx
, dy
, alignlinevector
, alignpointvector
):
187 n
= math
.sqrt(dx
* dx
+ dy
* dy
)
188 dx
, dy
= dx
/ n
, dy
/ n
189 linevectors
= map(lambda (p1
, p2
), self
=self
, a
=a
, dx
=dx
, dy
=dy
, alignlinevector
=alignlinevector
:
190 alignlinevector(a
, dx
, dy
, *(p1
+ p2
)), self
.successivepoints())
191 for linevector
in linevectors
:
192 if type(linevector
) is types
.TupleType
:
194 for i
, j
in self
.successivepointnumbers():
195 l1
, l2
= linevectors
[i
], linevectors
[j
]
196 if (l1
is not None or l2
is not None) and (l1
== 1 or l1
is None) and (l2
== 0 or l2
is None):
197 return alignpointvector(a
, dx
, dy
, *self
.successivepoints()[j
][0])
200 def _circlealignvector(self
, a
, dx
, dy
):
201 return self
._alignvector
(a
, dx
, dy
, self
._circlealignlinevector
, self
._circlealignpointvector
)
203 def _linealignvector(self
, a
, dx
, dy
):
204 return self
._alignvector
(a
, dx
, dy
, self
._linealignlinevector
, self
._linealignpointvector
)
206 def circlealignvector(self
, a
, dx
, dy
):
207 return map(unit
.t_pt
, self
._circlealignvector
(unit
.topt(a
), dx
, dy
))
209 def linealignvector(self
, a
, dx
, dy
):
210 return map(unit
.t_pt
, self
._linealignvector
(unit
.topt(a
), dx
, dy
))
212 def _circlealign(self
, *args
):
213 self
.transform(trafo
._translate
(*self
._circlealignvector
(*args
)))
216 def _linealign(self
, *args
):
217 self
.transform(trafo
._translate
(*self
._linealignvector
(*args
)))
220 def circlealign(self
, *args
):
221 self
.transform(trafo
.translate(*self
.circlealignvector(*args
)))
224 def linealign(self
, *args
):
225 self
.transform(trafo
.translate(*self
.linealignvector(*args
)))
228 def _extent(self
, dx
, dy
):
229 n
= math
.sqrt(dx
* dx
+ dy
* dy
)
230 dx
, dy
= dx
/ n
, dy
/ n
231 oldcenter
= self
.center
232 if self
.center
is None:
234 x1
, y1
= self
._linealignvector
(0, dx
, dy
)
235 x2
, y2
= self
._linealignvector
(0, -dx
, -dy
)
236 self
.center
= oldcenter
237 return (x1
-x2
)*dx
+ (y1
-y2
)*dy
239 def extent(self
, dx
, dy
):
240 return unit
.t_pt(self
._extent
(dx
, dy
))
242 def _pointdistance(self
, x
, y
):
244 for p1
, p2
in self
.successivepoints():
245 gx
, gy
= p2
[0] - p1
[0], p2
[1] - p1
[1]
246 if gx
* gx
+ gy
* gy
< 1e-10:
247 dx
, dy
= p1
[0] - x
, p1
[1] - y
249 a
= (gx
* (x
- p1
[0]) + gy
* (y
- p1
[1])) / (gx
* gx
+ gy
* gy
)
251 dx
, dy
= p1
[0] - x
, p1
[1] - y
253 dx
, dy
= p2
[0] - x
, p2
[1] - y
255 dx
, dy
= x
- p1
[0] - a
* gx
, y
- p1
[1] - a
* gy
256 new
= math
.sqrt(dx
* dx
+ dy
* dy
)
257 if result
is None or new
< result
:
261 def pointdistance(self
, x
, y
):
262 return unit
.t_pt(self
._pointdistance
(unit
.topt(x
), unit
.topt(y
)))
264 def _boxdistance(self
, other
, epsilon
=1e-10):
265 # XXX: boxes crossing and distance calculation is O(N^2)
266 for p1
, p2
in self
.successivepoints():
267 for p3
, p4
in other
.successivepoints():
268 a
= (p4
[1] - p3
[1]) * (p3
[0] - p1
[0]) - (p4
[0] - p3
[0]) * (p3
[1] - p1
[1])
269 b
= (p2
[1] - p1
[1]) * (p3
[0] - p1
[0]) - (p2
[0] - p1
[0]) * (p3
[1] - p1
[1])
270 c
= (p2
[0] - p1
[0]) * (p4
[1] - p3
[1]) - (p2
[1] - p1
[1]) * (p4
[0] - p3
[0])
271 if (abs(c
) > 1e-10 and
272 a
/ c
> -epsilon
and a
/ c
< 1 + epsilon
and
273 b
/ c
> -epsilon
and b
/ c
< 1 + epsilon
):
276 for x
, y
in other
.corners
:
277 new
= self
._pointdistance
(x
, y
)
278 if result
is None or new
< result
:
280 for x
, y
in self
.corners
:
281 new
= other
._pointdistance
(x
, y
)
282 if result
is None or new
< result
:
286 def boxdistance(self
, other
):
287 return unit
.t_pt(self
._boxdistance
(other
))
290 return bbox
._bbox
(min([x
[0] for x
in self
.corners
]),
291 min([x
[1] for x
in self
.corners
]),
292 max([x
[0] for x
in self
.corners
]),
293 max([x
[1] for x
in self
.corners
]))
296 def _genericalignequal(method
, polygons
, a
, dx
, dy
):
299 v
= method(p
, a
, dx
, dy
)
300 if vec
is None or vec
[0] * dx
+ vec
[1] * dy
< v
[0] * dx
+ v
[1] * dy
:
303 p
.transform(trafo
._translate
(*vec
))
306 def _circlealignequal(polygons
, *args
):
307 _genericalignequal(_polygon
._circlealignvector
, polygons
, *args
)
309 def _linealignequal(polygons
, *args
):
310 _genericalignequal(_polygon
._linealignvector
, polygons
, *args
)
312 def circlealignequal(polygons
, a
, *args
):
313 _circlealignequal(polygons
, unit
.topt(a
), *args
)
315 def linealignequal(polygons
, a
, *args
):
316 _linealignequal(polygons
, unit
.topt(a
), *args
)
319 def _tile(polygons
, a
, dx
, dy
):
320 maxextent
= polygons
[0]._extent
(dx
, dy
)
321 for p
in polygons
[1:]:
322 extent
= p
._extent
(dx
, dy
)
323 if extent
> maxextent
:
327 p
.transform(trafo
._translate
(d
*dx
, d
*dy
))
331 def tile(polygons
, a
, dx
, dy
):
332 _tile(polygons
, unit
.topt(a
), dx
, dy
)
335 class polygon(_polygon
):
337 def __init__(self
, corners
=None, center
=None, **args
):
338 corners
= [[unit
.topt(x
) for x
in corner
] for corner
in corners
]
339 if center
is not None:
340 center
= map(unit
.topt
, center
)
341 _polygon
.__init
__(self
, corners
=corners
, center
=center
, **args
)
344 class _rect(_polygon
):
346 def __init__(self
, x
, y
, width
, height
, relcenter
=(0, 0), abscenter
=(0, 0),
347 corners
=helper
.nodefault
, center
=helper
.nodefault
, **args
):
348 if corners
!= helper
.nodefault
or center
!= helper
.nodefault
:
350 _polygon
.__init
__(self
, corners
=((x
, y
),
352 (x
+ width
, y
+ height
),
354 center
=(x
+ relcenter
[0] * width
+ abscenter
[0],
355 y
+ relcenter
[1] * height
+ abscenter
[1]),
361 def __init__(self
, x
, y
, width
, height
, relcenter
=(0, 0), abscenter
=(0, 0), **args
):
362 _rect
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), unit
.topt(width
), unit
.topt(height
),
363 relcenter
=relcenter
, abscenter
=(unit
.topt(abscenter
[0]), unit
.topt(abscenter
[1])), **args
)