1 # -*- coding: ISO-8859-1 -*-
3 # Copyright (C) 2011 Michael Schindler <m-schindler@users.sourceforge.net>
5 # This file is part of PyX (http://pyx.sourceforge.net/).
7 # PyX is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # PyX is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with PyX; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21 from math
import atan2
, sin
, cos
, sqrt
, pi
23 ################################################################################
24 # Internal functions of MetaPost
26 # This file re-implements some of the functionality of MetaPost
27 # (http://tug.org/metapost). MetaPost was developed by John D. Hobby
30 # This file is based on the MetaPost version distributed by TeXLive:
31 # svn://tug.org/texlive/trunk/Build/source/texk/web2c/mplibdir
32 # revision 22737 (2011-05-31)
33 ################################################################################
50 three_sixty_deg
= 2*pi
52 def mp_make_choices(knots
): # <<<
53 """Implements mp_make_choices from metapost (mp.c)"""
54 # 334: If consecutive knots are equal, join them explicitly
58 # XXX float comparison: use epsilon
59 if p
.x_pt
== q
.x_pt
and p
.y_pt
== q
.y_pt
and p
.rtype
> mp_explicit
:
61 if p
.ltype
== mp_open
:
63 p
.set_left_curl(unity
)
65 if q
.rtype
== mp_open
:
67 q
.set_right_curl(unity
)
77 # If there are no breakpoints, it is necessary to compute the direction angles around an entire cycle.
78 # In this case the mp left type of the first node is temporarily changed to end cycle.
79 # Find the first breakpoint, h, on the path
80 # insert an artificial breakpoint if the path is an unbroken cycle
83 if h
.ltype
!= mp_open
or h
.rtype
!= mp_open
:
87 h
.ltype
= mp_end_cycle
93 # Fill in the control points between p and the next breakpoint, then advance p to that breakpoint
95 if p
.rtype
>= mp_given
:
96 while q
.ltype
== mp_open
and q
.rtype
== mp_open
:
100 # Calculate the turning angles psi_k and the distances d(k, k+1)
101 # set n to the length of the path
104 n
= knots
.linked_len()
105 delta_x
, delta_y
, delta
, psi
= [], [], [], [None]
108 assert len(delta_x
) == k
109 delta_x
.append(t
.x_pt
- s
.x_pt
)
110 delta_y
.append(t
.y_pt
- s
.y_pt
)
111 delta
.append(mp_pyth_add(delta_x
[k
], delta_y
[k
]))
113 sine
= mp_make_fraction(delta_y
[k
-1], delta
[k
-1])
114 cosine
= mp_make_fraction(delta_x
[k
-1], delta
[k
-1])
116 mp_take_fraction(delta_x
[k
], cosine
) + mp_take_fraction(delta_y
[k
], sine
),
117 mp_take_fraction(delta_y
[k
], cosine
) - mp_take_fraction(delta_x
[k
], sine
)))
122 if k
>= n
and s
.ltype
!= mp_end_cycle
:
130 # 347: Remove open types at the breakpoints
131 if q
.ltype
== mp_open
:
132 delx_pt
= q
.rx_pt
- q
.x_pt
133 dely_pt
= q
.ry_pt
- q
.y_pt
134 if delx_pt
== 0 and dely_pt
== 0: # XXX float comparison
136 q
.set_left_curl(unity
)
139 q
.set_left_given(mp_n_arg(delx_pt
, dely_pt
))
141 if p
.rtype
== mp_open
and p
.ltype
== mp_explicit
:
142 delx_pt
= p
.x_pt
- p
.lx_pt
143 dely_pt
= p
.y_pt
- p
.ly_pt
144 if delx_pt
== 0 and dely_pt
== 0: # XXX float comparison
146 p
.set_right_curl(unity
)
149 p
.set_right_given(mp_n_arg(delx_pt
, dely_pt
))
151 # call the internal solving routine
152 mp_solve_choices(p
, q
, n
, delta_x
, delta_y
, delta
, psi
)
154 elif p
.rtype
== mp_endpoint
:
155 # 337: Give reasonable values for the unused control points between p and q
165 def mp_solve_choices(p
, q
, n
, delta_x
, delta_y
, delta
, psi
): # <<<
166 """Implements mp_solve_choices form metapost (mp.c)"""
167 uu
= [None]*(len(delta
)+1)
168 vv
= [None]*len(uu
) # angles
170 theta
= [None]*len(uu
)
171 # k current knot number
172 # r, s, t registers for list traversal
180 # Get the linear equations started
181 # or return with the control points in place, if linear equations needn't be solved
183 if s
.rtype
== mp_given
: # <<<
184 if t
.ltype
== mp_given
:
185 # 372: Reduce to simple case of two givens and return
186 aa
= mp_n_arg(delta_x
[0], delta_y
[0])
187 ct
, st
= mp_n_sin_cos(p
.right_given() - aa
)
188 cf
, sf
= mp_n_sin_cos(q
.left_given() - aa
)
189 mp_set_controls(p
, q
, delta_x
[0], delta_y
[0], st
, ct
, -sf
, cf
)
193 vv
[0] = s
.right_given() - mp_n_arg(delta_x
[0], delta_y
[0])
194 vv
[0] = reduce_angle(vv
[0])
198 elif s
.rtype
== mp_curl
: # <<<
199 if t
.ltype
== mp_curl
:
200 # 373: (mp.pdf) Reduce to simple case of straight line and return
201 p
.rtype
= mp_explicit
202 q
.ltype
= mp_explicit
203 lt
= abs(q
.left_tension())
204 rt
= abs(p
.right_tension())
207 p
.rx_pt
= p
.x_pt
+ delta_x
[0]/3.0
209 p
.rx_pt
= p
.x_pt
+ delta_x
[0]/3.0
211 p
.ry_pt
= p
.y_pt
+ delta_y
[0]/3.0
213 p
.ry_pt
= p
.y_pt
+ delta_y
[0]/3.0
215 ff
= mp_make_fraction(unity
, 3.0*rt
)
216 p
.rx_pt
= p
.x_pt
+ mp_take_fraction(delta_x
[0], ff
)
217 p
.ry_pt
= p
.y_pt
+ mp_take_fraction(delta_y
[0], ff
)
221 q
.lx_pt
= q
.x_pt
- delta_x
[0]/3.0
223 q
.lx_pt
= q
.x_pt
- delta_x
[0]/3.0
225 q
.ly_pt
= q
.y_pt
- delta_y
[0]/3.0
227 q
.ly_pt
= q
.y_pt
- delta_y
[0]/3.0
229 ff
= mp_make_fraction(unity
, 3.0*lt
)
230 q
.lx_pt
= q
.x_pt
- mp_take_fraction(delta_x
[0], ff
)
231 q
.ly_pt
= q
.y_pt
- mp_take_fraction(delta_y
[0], ff
)
234 else: # t.ltype != mp_curl
237 lt
= abs(t
.left_tension())
238 rt
= abs(s
.right_tension())
239 if rt
== unity
and lt
== unity
: # XXX float comparison
240 uu
[0] = mp_make_fraction(cc
+ cc
+ unity
, cc
+ two
)
242 uu
[0] = mp_curl_ratio(cc
, rt
, lt
)
243 vv
[0] = -mp_take_fraction(psi
[1], uu
[0])
246 elif s
.rtype
== mp_open
: # <<<
254 if s
.ltype
== mp_end_cycle
or s
.ltype
== mp_open
: # <<<
255 # 356: Set up equation to match mock curvatures at z_k;
256 # then finish loop with theta_n adjusted to equal theta_0, if a
259 # 357: Calculate the values
260 # aa = Ak/Bk, bb = Dk/Ck, dd = (3-alpha_{k-1})d(k,k+1),
261 # ee = (3-beta_{k+1})d(k-1,k), cc=(Bk-uk-Ak)/Bk
262 if abs(r
.right_tension()) == unity
: # XXX float comparison
266 aa
= mp_make_fraction(unity
, 3*abs(r
.right_tension()) - unity
)
267 dd
= mp_take_fraction(delta
[k
],
268 fraction_three
- mp_make_fraction(unity
, abs(r
.right_tension())))
269 if abs(t
.left_tension()) == unity
: # XXX float comparison
273 bb
= mp_make_fraction(unity
, 3*abs(t
.left_tension()) - unity
)
274 ee
= mp_take_fraction(delta
[k
-1],
275 fraction_three
- mp_make_fraction(unity
, abs(t
.left_tension())))
276 cc
= fraction_one
- mp_take_fraction(uu
[k
-1], aa
)
278 # 358: Calculate the ratio ff = Ck/(Ck + Bk - uk-1Ak)
279 dd
= mp_take_fraction(dd
, cc
)
280 lt
= abs(s
.left_tension())
281 rt
= abs(s
.right_tension())
284 ff
= mp_make_fraction(lt
, rt
)
285 ff
= mp_take_fraction(ff
, ff
)
286 dd
= mp_take_fraction(dd
, ff
)
288 ff
= mp_make_fraction(rt
, lt
)
289 ff
= mp_take_fraction(ff
, ff
)
290 ee
= mp_take_fraction(ee
, ff
)
291 ff
= mp_make_fraction(ee
, ee
+ dd
)
293 uu
[k
] = mp_take_fraction(ff
, bb
)
295 # 359: Calculate the values of vk and wk
296 acc
= -mp_take_fraction(psi
[k
+1], uu
[k
])
297 if r
.rtype
== mp_curl
:
299 vv
[k
] = acc
- mp_take_fraction(psi
[1], fraction_one
- ff
)
301 ff
= mp_make_fraction(fraction_one
- ff
, cc
)
302 acc
= acc
- mp_take_fraction(psi
[k
], ff
)
303 ff
= mp_take_fraction(ff
, aa
)
304 vv
[k
] = acc
- mp_take_fraction(vv
[k
-1], ff
)
308 ww
[k
] = -mp_take_fraction(ww
[k
-1], ff
)
310 if s
.ltype
== mp_end_cycle
:
311 # 360: Adjust theta_n to equal theta_0 and finish loop
319 aa
= vv
[k
] - mp_take_fraction(aa
, uu
[k
])
320 bb
= ww
[k
] - mp_take_fraction(bb
, uu
[k
])
323 aa
= mp_make_fraction(aa
, fraction_one
- bb
)
326 for k
in range(1, n
):
327 vv
[k
] = vv
[k
] + mp_take_fraction(aa
, ww
[k
])
330 elif s
.ltype
== mp_curl
: # <<<
333 lt
= abs(s
.left_tension())
334 rt
= abs(r
.right_tension())
335 if rt
== unity
and lt
== unity
:
336 ff
= mp_make_fraction(cc
+ cc
+ unity
, cc
+ two
)
338 ff
= mp_curl_ratio(cc
, lt
, rt
)
339 theta
[n
] = -mp_make_fraction(mp_take_fraction(vv
[n
-1], ff
),
340 fraction_one
- mp_take_fraction(ff
, uu
[n
-1]))
343 elif s
.ltype
== mp_given
: # <<<
345 theta
[n
] = s
.left_given() - mp_n_arg(delta_x
[n
-1], delta_y
[n
-1])
346 theta
[n
] = reduce_angle(theta
[n
])
350 # end of k == 0, k != 0 >>>
357 # Finish choosing angles and assigning control points
358 for k
in range(n
-1, -1, -1):
359 theta
[k
] = vv
[k
] - mp_take_fraction(theta
[k
+1], uu
[k
])
364 ct
, st
= mp_n_sin_cos(theta
[k
])
365 cf
, sf
= mp_n_sin_cos(-psi
[k
+1]-theta
[k
+1])
366 mp_set_controls(s
, t
, delta_x
[k
], delta_y
[k
], st
, ct
, sf
, cf
)
372 def mp_n_arg(x
, y
): # <<<
375 def mp_n_sin_cos(z
): # <<<
376 """Given an integer z that is 2**20 times an angle theta in degrees, the
377 purpose of n sin cos(z) is to set x = r cos theta and y = r sin theta
378 (approximately), for some rather large number r. The maximum of x and y
379 will be between 2**28 and 2**30, so that there will be hardly any loss of
380 accuracy. Then x and y are divided by r."""
382 return cos(z
), sin(z
)
384 def mp_set_controls(p
, q
, delta_x
, delta_y
, st
, ct
, sf
, cf
): # <<<
385 """The set controls routine actually puts the control points into a pair of
386 consecutive nodes p and q. Global variables are used to record the values
387 of sin(theta), cos(theta), sin(phi), and cos(phi) needed in this
390 See mp.pdf, item 370"""
391 lt
= abs(q
.left_tension())
392 rt
= abs(p
.right_tension())
393 rr
= mp_velocity(st
, ct
, sf
, cf
, rt
)
394 ss
= mp_velocity(sf
, cf
, st
, ct
, lt
)
395 if p
.right_tension() < 0 or q
.left_tension() < 0:
396 # 371: Decrease the velocities, if necessary, to stay inside the bounding triangle
397 # this is the only place where the sign of the tension counts
398 if (st
>= 0 and sf
>= 0) or (st
<= 0 and sf
<= 0):
399 sine
= mp_take_fraction(abs(st
), cf
) + mp_take_fraction(abs(sf
), ct
) # sin(theta+phi)
401 #sine = mp_take_fraction(sine, fraction_one + unity) # safety factor
402 sine
*= 1.00024414062 # safety factor
403 if p
.right_tension() < 0:
404 if mp_ab_vs_cd(abs(sf
), fraction_one
, rr
, sine
) < 0:
405 rr
= mp_make_fraction(abs(sf
), sine
)
406 if q
.left_tension() < 0:
407 if mp_ab_vs_cd(abs(st
), fraction_one
, ss
, sine
) < 0:
408 ss
= mp_make_fraction(abs(st
), sine
)
410 p
.rx_pt
= p
.x_pt
+ mp_take_fraction(mp_take_fraction(delta_x
, ct
) - mp_take_fraction(delta_y
, st
), rr
)
411 p
.ry_pt
= p
.y_pt
+ mp_take_fraction(mp_take_fraction(delta_y
, ct
) + mp_take_fraction(delta_x
, st
), rr
)
412 q
.lx_pt
= q
.x_pt
- mp_take_fraction(mp_take_fraction(delta_x
, cf
) + mp_take_fraction(delta_y
, sf
), ss
)
413 q
.ly_pt
= q
.y_pt
- mp_take_fraction(mp_take_fraction(delta_y
, cf
) - mp_take_fraction(delta_x
, sf
), ss
)
414 p
.rtype
= mp_explicit
415 q
.ltype
= mp_explicit
417 def mp_make_fraction(p
, q
): # <<<
419 """The make fraction routine produces the fraction equivalent of p/q, given
420 integers p and q; it computes the integer f = floor(2**28 p/q + 1/2), when
421 p and q are positive.
423 In machine language this would simply be (2**28*p)div q"""
426 def mp_take_fraction(q
, f
): # <<<
428 """The dual of make fraction is take fraction, which multiplies a given
429 integer q by a fraction f. When the operands are positive, it computes
430 p = floor(q*f/2**28 + 1/2), a symmetric function of q and f."""
433 def mp_pyth_add(a
, b
): # <<<
435 """Pythagorean addition sqrt(a**2 + b**2) is implemented by an elegant
436 iterative scheme due to Cleve Moler and Donald Morrison [IBM Journal of
437 Research and Development 27 (1983), 577-581]. It modifies a and b in such a
438 way that their Pythagorean sum remains invariant, while the smaller
439 argument decreases."""
440 return sqrt(a
*a
+ b
*b
)
442 def mp_curl_ratio(gamma
, a_tension
, b_tension
): # <<<
443 """The curl ratio subroutine has three arguments, which our previous
444 notation encourages us to call gamma, 1/alpha, and 1/beta. It is a somewhat
445 tedious program to calculate
446 [(3-alpha)alpha^2 gamma + beta^3] / [alpha^3 gamma + (3-beta)beta^2],
447 with the result reduced to 4 if it exceeds 4. (This reduction of curl is
448 necessary only if the curl and tension are both large.) The values of alpha
449 and beta will be at most 4/3.
451 See mp.pdf (items 365, 366)."""
452 alpha
= 1.0/a_tension
454 return min(4.0, ((3.0-alpha
)*alpha
**2*gamma
+ beta
**3) /
455 (alpha
**3*gamma
+ (3.0-beta
)*beta
**2))
457 def mp_ab_vs_cd(a
, b
, c
, d
): # <<<
458 """Tests rigorously if ab is greater than, equal to, or less than cd, given
459 integers (a, b, c, d). In most cases a quick decision is reached. The
460 result is +1, 0, or -1 in the three respective cases.
461 See mpmath.pdf (item 33)."""
468 def mp_velocity(st
, ct
, sf
, cf
, t
): # <<<
469 """Metapost's standard velocity subroutine for cubic Bezier curves.
470 See mpmath.pdf (item 30) and mp.pdf (item 339)."""
471 return min(4.0, (2.0 + sqrt(2)*(st
-sf
/16.0)*(sf
-st
/16.0)*(ct
-cf
)) /
472 (1.5*t
*(2+(sqrt(5)-1)*ct
+ (3-sqrt(5))*cf
)))
474 def reduce_angle(A
): # <<<
475 """A macro in mp.c"""
476 if abs(A
) > one_eighty_deg
:
484 # vim:foldmethod=marker:foldmarker=<<<,>>>