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
36 def path(self
, centerradius
=None, bezierradius
=None, beziersoftness
=1):
38 if centerradius
is not None and self
.center
is not None:
39 r
= unit
.topt(unit
.length(centerradius
, default_type
="v"))
40 pathels
.append(path
._arc
(self
.center
[0], self
.center
[1], r
, 0, 360))
41 pathels
.append(path
.closepath())
42 if bezierradius
is None:
43 pathels
.append(path
._moveto
(self
.corners
[0][0], self
.corners
[0][1]))
44 for x
, y
in self
.corners
[1:]:
45 pathels
.append(path
._lineto
(x
, y
))
46 pathels
.append(path
.closepath())
48 # curved box plotting by Michael Schindler
51 r
= unit
.topt(bezierradius
)
55 n
= math
.sqrt(v
[0] * v
[0] + v
[1] * v
[1])
56 return v
[0] / n
, v
[1] / n
57 d1
= normed(self
.corners
[(i
- 1 + l
) % l
][0] - c
[0],
58 self
.corners
[(i
- 1 + l
) % l
][1] - c
[1])
59 d2
= normed(self
.corners
[(i
+ 1 + l
) % l
][0] - c
[0],
60 self
.corners
[(i
+ 1 + l
) % l
][1] - c
[1])
61 dc
= normed(d1
[0] + d2
[0], d1
[1] + d2
[1])
63 g
= 1.25 * f
+ math
.sqrt(1.5625 * f
* f
+ f
* r
/ 6.0)
64 e
= f
* math
.sqrt(0.5 + 0.5 * (d1
[0] * d2
[0] + d1
[1] * d2
[1]))
65 e
= c
[0] + e
* dc
[0], c
[1] + e
* dc
[1]
66 f1
= c
[0] + f
* d1
[0], c
[1] + f
* d1
[1]
67 f2
= c
[0] + f
* d2
[0], c
[1] + f
* d2
[1]
68 g1
= c
[0] + g
* d1
[0], c
[1] + g
* d1
[1]
69 g2
= c
[0] + g
* d2
[0], c
[1] + g
* d2
[1]
70 d1
= c
[0] + r
* d1
[0], c
[1] + r
* d1
[1]
71 d2
= c
[0] + r
* d2
[0], c
[1] + r
* d2
[1]
73 pathels
.append(path
._lineto
(*d1
))
75 pathels
.append(path
._moveto
(*d1
))
76 pathels
.append(path
._curveto
(*(g1
+ f1
+ e
)))
77 pathels
.append(path
._curveto
(*(f2
+ g2
+ d2
)))
78 pathels
.append(path
.closepath())
79 return path
.path(*pathels
)
81 def transform(self
, *trafos
):
83 if self
.center
is not None:
84 self
.center
= trafo
._apply
(*self
.center
)
85 self
.corners
= [trafo
._apply
(*point
) for point
in self
.corners
]
87 def reltransform(self
, *trafos
):
88 if self
.center
is not None:
89 trafos
= ([trafo
._translate
(-self
.center
[0], -self
.center
[1])] +
91 [trafo
._translate
(self
.center
[0], self
.center
[1])])
92 self
.transform(*trafos
)
94 def successivepointnumbers(self
):
95 return [i
and (i
- 1, i
) or (len(self
.corners
) - 1, 0) for i
in range(len(self
.corners
))]
97 def successivepoints(self
):
98 return [(self
.corners
[i
], self
.corners
[j
]) for i
, j
in self
.successivepointnumbers()]
100 def _circlealignlinevector(self
, a
, dx
, dy
, ex
, ey
, fx
, fy
, epsilon
=1e-10):
102 gx
, gy
= ex
- fx
, ey
- fy
# direction vector
103 if gx
*gx
+ gy
*gy
< epsilon
: # zero line length
104 return None # no solution -> return None
105 rsplit
= (dx
*gx
+ dy
*gy
) * 1.0 / (gx
*gx
+ gy
*gy
)
106 bx
, by
= dx
- gx
* rsplit
, dy
- gy
* rsplit
107 if bx
*bx
+ by
*by
< epsilon
: # zero projection
108 return None # no solution -> return None
109 if bx
*gy
- by
*gx
< 0: # half space
110 return None # no solution -> return None
111 sfactor
= math
.sqrt((dx
*dx
+ dy
*dy
) / (bx
*bx
+ by
*by
))
112 bx
, by
= a
* bx
* sfactor
, a
* by
* sfactor
113 alpha
= ((bx
+cx
-ex
)*dy
- (by
+cy
-ey
)*dx
) * 1.0 / (gy
*dx
- gx
*dy
)
114 if alpha
> 0 - epsilon
and alpha
< 1 + epsilon
:
115 beta
= ((ex
-bx
-cx
)*gy
- (ey
-by
-cy
)*gx
) * 1.0 / (gx
*dy
- gy
*dx
)
116 return beta
*dx
, beta
*dy
# valid solution -> return align tuple
117 # crossing point at the line, but outside a valid range
119 return 0 # crossing point outside e
120 return 1 # crossing point outside f
122 def _linealignlinevector(self
, a
, dx
, dy
, ex
, ey
, fx
, fy
, epsilon
=1e-10):
124 gx
, gy
= ex
- fx
, ey
- fy
# direction vector
125 if gx
*gx
+ gy
*gy
< epsilon
: # zero line length
126 return None # no solution -> return None
127 if gy
*dx
- gx
*dy
< -epsilon
: # half space
128 return None # no solution -> return None
129 if dx
*gx
+ dy
*gy
> epsilon
or dx
*gx
+ dy
*gy
< -epsilon
:
130 if dx
*gx
+ dy
*gy
< 0: # angle bigger 90 degree
131 return 0 # use point e
132 return 1 # use point f
133 # a and g are othorgonal
134 alpha
= ((a
*dx
+cx
-ex
)*dy
- (a
*dy
+cy
-ey
)*dx
) * 1.0 / (gy
*dx
- gx
*dy
)
135 if alpha
> 0 - epsilon
and alpha
< 1 + epsilon
:
136 beta
= ((ex
-a
*dx
-cx
)*gy
- (ey
-a
*dy
-cy
)*gx
) * 1.0 / (gx
*dy
- gy
*dx
)
137 return beta
*dx
, beta
*dy
# valid solution -> return align tuple
138 # crossing point at the line, but outside a valid range
140 return 0 # crossing point outside e
141 return 1 # crossing point outside f
143 def _circlealignpointvector(self
, a
, dx
, dy
, px
, py
, epsilon
=1e-10):
147 p
= 2 * ((px
-cx
)*dx
+ (py
-cy
)*dy
)
148 q
= ((px
-cx
)*(px
-cx
) + (py
-cy
)*(py
-cy
) - a
*a
)
152 alpha
= - p
/ 2 + math
.sqrt(p
*p
/4 - q
)
154 alpha
= - p
/ 2 - math
.sqrt(p
*p
/4 - q
)
155 return alpha
*dx
, alpha
*dy
157 def _linealignpointvector(self
, a
, dx
, dy
, px
, py
):
159 beta
= (a
*dx
+cx
-px
)*dy
- (a
*dy
+cy
-py
)*dx
160 return a
*dx
- beta
*dy
- px
+ cx
, a
*dy
+ beta
*dx
- py
+ cy
162 def _alignvector(self
, a
, dx
, dy
, alignlinevector
, alignpointvector
):
163 n
= math
.sqrt(dx
* dx
+ dy
* dy
)
164 dx
, dy
= dx
/ n
, dy
/ n
165 linevectors
= map(lambda (p1
, p2
), self
=self
, a
=a
, dx
=dx
, dy
=dy
, alignlinevector
=alignlinevector
:
166 alignlinevector(a
, dx
, dy
, *(p1
+ p2
)), self
.successivepoints())
167 for linevector
in linevectors
:
168 if type(linevector
) is types
.TupleType
:
170 for i
, j
in self
.successivepointnumbers():
171 l1
, l2
= linevectors
[i
], linevectors
[j
]
172 if (l1
is not None or l2
is not None) and (l1
== 1 or l1
is None) and (l2
== 0 or l2
is None):
173 return alignpointvector(a
, dx
, dy
, *self
.successivepoints()[j
][0])
176 def _circlealignvector(self
, a
, dx
, dy
):
177 return self
._alignvector
(a
, dx
, dy
, self
._circlealignlinevector
, self
._circlealignpointvector
)
179 def _linealignvector(self
, a
, dx
, dy
):
180 return self
._alignvector
(a
, dx
, dy
, self
._linealignlinevector
, self
._linealignpointvector
)
182 def circlealignvector(self
, a
, dx
, dy
):
183 return map(unit
.t_pt
, self
._circlealignvector
(unit
.topt(a
), dx
, dy
))
185 def linealignvector(self
, a
, dx
, dy
):
186 return map(unit
.t_pt
, self
._linealignvector
(unit
.topt(a
), dx
, dy
))
188 def _circlealign(self
, *args
):
189 self
.transform(trafo
._translate
(*self
._circlealignvector
(*args
)))
192 def _linealign(self
, *args
):
193 self
.transform(trafo
._translate
(*self
._linealignvector
(*args
)))
196 def circlealign(self
, *args
):
197 self
.transform(trafo
.translate(*self
.circlealignvector(*args
)))
200 def linealign(self
, *args
):
201 self
.transform(trafo
.translate(*self
.linealignvector(*args
)))
204 def _extent(self
, dx
, dy
):
205 n
= math
.sqrt(dx
* dx
+ dy
* dy
)
206 dx
, dy
= dx
/ n
, dy
/ n
207 oldcenter
= self
.center
208 if self
.center
is None:
210 x1
, y1
= self
._linealignvector
(0, dx
, dy
)
211 x2
, y2
= self
._linealignvector
(0, -dx
, -dy
)
212 self
.center
= oldcenter
213 return (x1
-x2
)*dx
+ (y1
-y2
)*dy
215 def extent(self
, dx
, dy
):
216 return unit
.t_pt(self
._extent
(dx
, dy
))
218 def _pointdistance(self
, x
, y
):
220 for p1
, p2
in self
.successivepoints():
221 gx
, gy
= p2
[0] - p1
[0], p2
[1] - p1
[1]
222 if gx
* gx
+ gy
* gy
< 1e-10:
223 dx
, dy
= p1
[0] - x
, p1
[1] - y
225 a
= (gx
* (x
- p1
[0]) + gy
* (y
- p1
[1])) / (gx
* gx
+ gy
* gy
)
227 dx
, dy
= p1
[0] - x
, p1
[1] - y
229 dx
, dy
= p2
[0] - x
, p2
[1] - y
231 dx
, dy
= x
- p1
[0] - a
* gx
, y
- p1
[1] - a
* gy
232 new
= math
.sqrt(dx
* dx
+ dy
* dy
)
233 if result
is None or new
< result
:
237 def pointdistance(self
, x
, y
):
238 return unit
.t_pt(self
._pointdistance
(unit
.topt(x
), unit
.topt(y
)))
240 def _boxdistance(self
, other
, epsilon
=1e-10):
241 # XXX: boxes crossing and distance calculation is O(N^2)
242 for p1
, p2
in self
.successivepoints():
243 for p3
, p4
in other
.successivepoints():
244 a
= (p4
[1] - p3
[1]) * (p3
[0] - p1
[0]) - (p4
[0] - p3
[0]) * (p3
[1] - p1
[1])
245 b
= (p2
[1] - p1
[1]) * (p3
[0] - p1
[0]) - (p2
[0] - p1
[0]) * (p3
[1] - p1
[1])
246 c
= (p2
[0] - p1
[0]) * (p4
[1] - p3
[1]) - (p2
[1] - p1
[1]) * (p4
[0] - p3
[0])
247 if (abs(c
) > 1e-10 and
248 a
/ c
> -epsilon
and a
/ c
< 1 + epsilon
and
249 b
/ c
> -epsilon
and b
/ c
< 1 + epsilon
):
252 for x
, y
in other
.corners
:
253 new
= self
._pointdistance
(x
, y
)
254 if result
is None or new
< result
:
256 for x
, y
in self
.corners
:
257 new
= other
._pointdistance
(x
, y
)
258 if result
is None or new
< result
:
262 def boxdistance(self
, other
):
263 return unit
.t_pt(self
._boxdistance
(other
))
266 return bbox
.bbox(min([x
[0] for x
in self
.corners
]),
267 min([x
[1] for x
in self
.corners
]),
268 max([x
[0] for x
in self
.corners
]),
269 max([x
[1] for x
in self
.corners
]))
272 def _genericalignequal(method
, polygons
, a
, dx
, dy
):
275 v
= method(p
, a
, dx
, dy
)
276 if vec
is None or vec
[0] * dx
+ vec
[1] * dy
< v
[0] * dx
+ v
[1] * dy
:
279 p
.transform(trafo
._translate
(*vec
))
282 def _circlealignequal(polygons
, *args
):
283 _genericalignequal(_polygon
._circlealignvector
, polygons
, *args
)
285 def _linealignequal(polygons
, *args
):
286 _genericalignequal(_polygon
._linealignvector
, polygons
, *args
)
288 def circlealignequal(polygons
, a
, *args
):
289 _circlealignequal(polygons
, unit
.topt(a
), *args
)
291 def linealignequal(polygons
, a
, *args
):
292 _linealignequal(polygons
, unit
.topt(a
), *args
)
295 def _tile(polygons
, a
, dx
, dy
):
296 maxextent
= polygons
[0]._extent
(dx
, dy
)
297 for p
in polygons
[1:]:
298 extent
= p
._extent
(dx
, dy
)
299 if extent
> maxextent
:
303 p
.transform(trafo
._translate
(d
*dx
, d
*dy
))
307 def tile(polygons
, a
, dx
, dy
):
308 _tile(polygons
, unit
.topt(a
), dx
, dy
)
311 class polygon(_polygon
):
313 def __init__(self
, corners
=None, center
=None, **args
):
314 corners
= [[unit
.topt(x
) for x
in corner
] for corner
in corners
]
315 if center
is not None:
316 center
= map(unit
.topt
, center
)
317 _polygon
.__init
__(self
, corners
=corners
, center
=center
, **args
)
320 class _rect(_polygon
):
322 def __init__(self
, x
, y
, width
, height
, relcenter
=(0, 0), abscenter
=(0, 0),
323 corners
=helper
.nodefault
, center
=helper
.nodefault
, **args
):
324 if corners
!= helper
.nodefault
or center
!= helper
.nodefault
:
326 _polygon
.__init
__(self
, corners
=((x
, y
),
328 (x
+ width
, y
+ height
),
330 center
=(x
+ relcenter
[0] * width
+ abscenter
[0],
331 y
+ relcenter
[1] * height
+ abscenter
[1]),
337 def __init__(self
, x
, y
, width
, height
, relcenter
=(0, 0), abscenter
=(0, 0), **args
):
338 _rect
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), unit
.topt(width
), unit
.topt(height
),
339 relcenter
=relcenter
, abscenter
=(unit
.topt(abscenter
[0]), unit
.topt(abscenter
[1])), **args
)