Browse Source

Add Spiro version 0.01.

Roozbeh Pournader 10 years ago
parent
commit
0f1bd27678

+ 339 - 0
third_party/spiro/LICENSE

@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 24 - 0
third_party/spiro/README

@@ -0,0 +1,24 @@
+Spiro release 0.01
+4 May 2007
+Raph Levien
+
+This is a very rough release of the Spiro toolset that I've been using
+to create my fonts. The main program is ppedit, and there's a more
+detailed README in that directory.
+
+To build ppedit, do:
+
+cd ppedit
+make
+
+It requires Gtk2 and cairo.
+
+The curves/ subdirectory contains python utilities for manipulating
+curves, including a Bezier optimizer.
+
+The font/ subdirectory contains tools for segmenting scans and
+compositing them into classes, suitable for tracing over. (Note,
+however, that the gtk2 build of ppedit doesn't yet load background
+images for tracing).
+
+Always see http://levien.com/spiro/ for more updates.

+ 10 - 0
third_party/spiro/README.third_party

@@ -0,0 +1,10 @@
+URL: http://www.levien.com/spiro/spiro-0.01.tar.gz
+Version: 0.01
+License: GPL v2 or later
+License File: LICENSE
+
+Description:
+Spiro is a toolkit for curve design, especially font design.
+
+Local Modifications:
+No modifications.

+ 85 - 0
third_party/spiro/curves/band.py

@@ -0,0 +1,85 @@
+# A little solver for band-diagonal matrices. Based on NR Ch 2.4.
+
+from math import *
+
+from Numeric import *
+
+do_pivot = True
+
+def bandec(a, m1, m2):
+    n, m = a.shape
+    mm = m1 + m2 + 1
+    if m != mm:
+        raise ValueError('Array has width %d expected %d' % (m, mm))
+    al = zeros((n, m1), Float)
+    indx = zeros(n, Int)
+
+    for i in range(m1):
+        l = m1 - i
+        for j in range(l, mm): a[i, j - l] = a[i, j]
+        for j in range(mm - l, mm): a[i, j] = 0
+
+    d = 1.
+
+    l = m1
+    for k in range(n):
+        dum = a[k, 0]
+        pivot = k
+        if l < n: l += 1
+        if do_pivot:
+            for j in range(k + 1, l):
+                if abs(a[j, 0]) > abs(dum):
+                    dum = a[j, 0]
+                    pivot = j
+        indx[k] = pivot
+        if dum == 0.: a[k, 0] = 1e-20
+        if pivot != k:
+            d = -d
+            for j in range(mm):
+                tmp = a[k, j]
+                a[k, j] = a[pivot, j]
+                a[pivot, j] = tmp
+        for i in range(k + 1, l):
+            dum = a[i, 0] / a[k, 0]
+            al[k, i - k - 1] = dum
+            for j in range(1, mm):
+                a[i, j - 1] = a[i, j] - dum * a[k, j]
+            a[i, mm - 1] = 0.
+    return al, indx, d
+
+def banbks(a, m1, m2, al, indx, b):
+    n, m = a.shape
+    mm = m1 + m2 + 1
+    l = m1
+    for k in range(n):
+        i = indx[k]
+        if i != k:
+            tmp = b[k]
+            b[k] = b[i]
+            b[i] = tmp
+        if l < n: l += 1
+        for i in range(k + 1, l):
+            b[i] -= al[k, i - k - 1] * b[k]
+    l = 1
+    for i in range(n - 1, -1, -1):
+        dum = b[i]
+        for k in range(1, l):
+            dum -= a[i, k] * b[k + i]
+        b[i] = dum / a[i, 0]
+        if l < mm: l += 1
+
+if __name__ == '__main__':
+    a = zeros((10, 3), Float)
+    for i in range(10):
+        a[i, 0] = 1
+        a[i, 1] = 2
+        a[i, 2] = 1
+    print a
+    al, indx, d = bandec(a, 1, 1)
+    print a
+    print al
+    print indx
+    b = zeros(10, Float)
+    b[5] = 1
+    banbks(a, 1, 1, al, indx, b)
+    print b

+ 183 - 0
third_party/spiro/curves/bezfigs.py

@@ -0,0 +1,183 @@
+import sys
+from math import *
+
+import fromcubic
+import tocubic
+
+import cornu
+
+def eps_prologue(x0, y0, x1, y1, draw_box = False):
+    print '%!PS-Adobe-3.0 EPSF'
+    print '%%BoundingBox:', x0, y0, x1, y1
+    print '%%EndComments'
+    print '%%EndProlog'
+    print '%%Page: 1 1'
+    if draw_box:
+        print x0, y0, 'moveto', x0, y1, 'lineto', x1, y1, 'lineto', x1, y0, 'lineto closepath stroke'
+
+def eps_trailer():
+    print '%%EOF'
+
+def fit_cubic_superfast(z0, z1, arclen, th0, th1, aab):
+    chord = hypot(z1[0] - z0[0], z1[1] - z0[1])
+    cth0, sth0 = cos(th0), sin(th0)
+    cth1, sth1 = -cos(th1), -sin(th1)
+    armlen = .66667 * arclen
+    a = armlen * aab
+    b = armlen - a
+    bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a),
+          (z1[0] + cth1 * b, z1[1] + sth1 * b), z1]
+    return bz
+
+def fit_cubic(z0, z1, arclen, th_fn, fast, aabmin = 0, aabmax = 1.):
+    chord = hypot(z1[0] - z0[0], z1[1] - z0[1])
+    if (arclen < 1.000001 * chord):
+        return [z0, z1], 0
+    th0 = th_fn(0)
+    th1 = th_fn(arclen)
+    imax = 4
+    jmax = 10
+    if fast:
+        imax = 1
+        jmax = 0
+    for i in range(imax):
+        for j in range(jmax + 1):
+            if jmax == 0:
+                aab = 0.5 * (aabmin + aabmax)
+            else:
+                aab = aabmin + (aabmax - aabmin) * j / jmax
+            if fast == 2:
+                bz = fit_cubic_superfast(z0, z1, arclen, th0, th1, aab)
+            else:
+                bz = tocubic.fit_cubic_arclen(z0, z1, arclen, th0, th1, aab)
+            score = tocubic.measure_bz_rk4(bz, arclen, th_fn)
+            print '% aab =', aab, 'score =', score
+            sys.stdout.flush()
+            if j == 0 or score < best_score:
+                best_score = score
+                best_aab = aab
+                best_bz = bz
+        daab = .06 * (aabmax - aabmin)
+        aabmin = max(0, best_aab - daab)
+        aabmax = min(1, best_aab + daab)
+        print '%--- best_aab =', best_aab
+    return best_bz, best_score
+
+def cornu_to_cubic(t0, t1, figno):
+    if figno == 1:
+        aabmin = 0
+        aabmax = 0.4
+    elif figno == 2:
+        aabmin = 0.5
+        aabmax = 1.
+    else:
+        aabmin = 0
+        aabmax = 1.
+    fast = 0
+    if figno == 3:
+        fast = 1
+    elif figno == 4:
+        fast = 2
+    def th_fn(s):
+        return (s + t0) ** 2
+    y0, x0 = cornu.eval_cornu(t0)
+    y1, x1 = cornu.eval_cornu(t1)
+    bz, score = fit_cubic((x0, y0), (x1, y1), t1 - t0, th_fn, fast, aabmin, aabmax)
+    return bz, score
+
+def plot_k_of_bz(bz):
+    dbz = tocubic.bz_deriv(bz)
+    ddbz = tocubic.bz_deriv(dbz)
+    cmd = 'moveto'
+    ss = [0]
+    def arclength_deriv(x, ss):
+        dx, dy = tocubic.bz_eval(dbz, x)
+        return [hypot(dx, dy)]
+    dt = 0.01
+    t = 0
+    for i in range(101):
+        dx, dy = tocubic.bz_eval(dbz, t)
+        ddx, ddy = tocubic.bz_eval(ddbz, t)
+        k = (ddy * dx - dy * ddx) / (dx * dx + dy * dy) ** 1.5
+        print 100 + 500 * ss[0], 100 + 200 * k, cmd
+        cmd = 'lineto'
+
+        dsdx = arclength_deriv(t, ss)
+        tocubic.rk4(ss, dsdx, t, .01, arclength_deriv)
+        t += dt
+    print 'stroke'
+
+def plot_k_nominal(s0, s1):
+    k0 = 2 * s0
+    k1 = 2 * s1
+    print 'gsave 0.5 setlinewidth'
+    print 100, 100 + 200 * k0, 'moveto'
+    print 100 + 500 * (s1 - s0), 100 + 200 * k1, 'lineto'
+    print 'stroke grestore'
+
+def simple_bez():
+    eps_prologue(95, 126, 552, 508, 0)
+    tocubic.plot_prolog()
+    print '/ss 1.5 def'
+    print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def'
+    bz, score = cornu_to_cubic(.5, 1.1, 2)
+    fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True)
+    print 'stroke'
+    print '/Times-Roman 12 selectfont'
+    print '95 130 moveto ((x0, y0)) show'
+    print '360 200 moveto ((x1, y1)) show'
+    print '480 340 moveto ((x2, y2)) show'
+    print '505 495 moveto ((x3, y3)) show'
+    print 'showpage'
+    eps_trailer()
+
+def fast_bez(figno):
+    if figno == 3:
+        y1 = 520
+    else:
+        y1 = 550
+    eps_prologue(95, 140, 552, y1, 0)
+    tocubic.plot_prolog()
+    print '/ss 1.5 def'
+    print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def'
+    bz, score = cornu_to_cubic(.5, 1.1, figno)
+    fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True)
+    print 'stroke'
+    plot_k_nominal(.5, 1.1)
+    plot_k_of_bz(bz)
+    print 'showpage'
+    eps_trailer()
+
+def bezfig(s1):
+    eps_prologue(95, 38, 510, 550, 0)
+    #print '0.5 0.5 scale 500 100 translate'
+    tocubic.plot_prolog()
+    print '/ss 1.5 def'
+    print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def'
+    bz, score = cornu_to_cubic(.5, 0.85, 1)
+    fromcubic.plot_bzs([[bz]], (-400, 0), 1000, True)
+    print 'stroke'
+    plot_k_nominal(.5, 0.85)
+    plot_k_of_bz(bz)
+    bz, score = cornu_to_cubic(.5, 0.85, 2)
+    fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True)
+    print 'stroke'
+    print 'gsave 0 50 translate'
+    plot_k_nominal(.5, .85)
+    plot_k_of_bz(bz)
+    print 'grestore'
+    print 'showpage'
+
+import sys
+
+if __name__ == '__main__':
+    figno = int(sys.argv[1])
+    if figno == 0:
+        simple_bez()
+    elif figno == 1:
+        bezfig(1.0)
+    elif figno == 2:
+        bezfig(0.85)
+    else:
+        fast_bez(figno)
+    #fast_bez(4)

+ 662 - 0
third_party/spiro/curves/bigmat.py

@@ -0,0 +1,662 @@
+# Solver based on direct Newton solving of 4 parameters for each curve
+# segment
+
+import sys
+from math import *
+
+from Numeric import *
+import LinearAlgebra as la
+
+import poly3
+import band
+
+class Seg:
+    def __init__(self, chord, th):
+        self.ks = [0., 0., 0., 0.]
+        self.chord = chord
+        self.th = th
+    def compute_ends(self, ks):
+        chord, ch_th = poly3.integ_chord(ks)
+        l = chord / self.chord
+        thl = ch_th - (-.5 * ks[0] + .125 * ks[1] - 1./48 * ks[2] + 1./384 * ks[3])
+        thr = (.5 * ks[0] + .125 * ks[1] + 1./48 * ks[2] + 1./384 * ks[3]) - ch_th
+        k0l = l * (ks[0] - .5 * ks[1] + .125 * ks[2] - 1./48 * ks[3])
+        k0r = l * (ks[0] + .5 * ks[1] + .125 * ks[2] + 1./48 * ks[3])
+        l2 = l * l
+        k1l = l2 * (ks[1] - .5 * ks[2] + .125 * ks[3])
+        k1r = l2 * (ks[1] + .5 * ks[2] + .125 * ks[3])
+        l3 = l2 * l
+        k2l = l3 * (ks[2] - .5 * ks[3])
+        k2r = l3 * (ks[2] + .5 * ks[3])
+        return (thl, k0l, k1l, k2l), (thr, k0r, k1r, k2r), l
+    def set_ends_from_ks(self):
+        self.endl, self.endr, self.l = self.compute_ends(self.ks)
+    def fast_pderivs(self):
+        l = self.l
+        l2 = l * l
+        l3 = l2 * l
+        return [((.5, l, 0, 0), (.5, l, 0, 0)),
+                ((-1./12, -l/2, l2, 0), (1./12, l/2, l2, 0)),
+                ((1./48, l/8, -l2/2, l3), (1./48, l/8, l2/2, l3)),
+                ((-1./480, -l/48, l2/8, -l3/2), (1./480, l/48, l2/8, l3/2))]
+    def compute_pderivs(self):
+        rd = 2e6
+        delta = 1./rd
+        base_ks = self.ks
+        base_endl, base_endr, dummy = self.compute_ends(base_ks)
+        result = []
+        for i in range(4):
+            try_ks = base_ks[:]
+            try_ks[i] += delta
+            try_endl, try_endr, dummy = self.compute_ends(try_ks)
+            deriv_l = (rd * (try_endl[0] - base_endl[0]),
+                       rd * (try_endl[1] - base_endl[1]),
+                       rd * (try_endl[2] - base_endl[2]),
+                       rd * (try_endl[3] - base_endl[3]))
+            deriv_r = (rd * (try_endr[0] - base_endr[0]),
+                       rd * (try_endr[1] - base_endr[1]),
+                       rd * (try_endr[2] - base_endr[2]),
+                       rd * (try_endr[3] - base_endr[3]))
+            result.append((deriv_l, deriv_r))
+        return result
+
+class Node:
+    def __init__(self, x, y, ty, th):
+        self.x = x
+        self.y = y
+        self.ty = ty
+        self.th = th
+    def continuity(self):
+        if self.ty == 'o':
+            return 4
+        elif self.ty in ('c', '[', ']'):
+            return 2
+        else:
+            return 0
+
+def mod_2pi(th):
+    u = th / (2 * pi)
+    return 2 * pi * (u - floor(u + 0.5))
+
+def setup_path(path):
+    segs = []
+    nodes = []
+    nsegs = len(path)
+    if path[0][2] == '{':
+        nsegs -= 1
+    for i in range(nsegs):
+        i1 = (i + 1) % len(path)
+        x0, y0, t0 = path[i]
+        x1, y1, t1 = path[i1]
+        s = Seg(hypot(y1 - y0, x1 - x0), atan2(y1 - y0, x1 - x0))
+        segs.append(s)
+    for i in range(len(path)):
+        x0, y0, t0 = path[i]
+
+        if t0 in ('{', '}', 'v'):
+            th = 0
+        else:
+            s0 = segs[(i + len(path) - 1) % len(path)]
+            s1 = segs[i]
+            th = mod_2pi(s1.th - s0.th)
+
+        n = Node(x0, y0, t0, th)
+        nodes.append(n)
+    return segs, nodes
+
+def count_vec(nodes):
+    jincs = []
+    n = 0
+    for i in range(len(nodes)):
+        i1 = (i + 1) % len(nodes)
+        t0 = nodes[i].ty
+        t1 = nodes[i1].ty
+        if t0 in ('{', '}', 'v', '[') and t1 in ('{', '}', 'v', ']'):
+            jinc = 0
+        elif t0 in ('{', '}', 'v', '[') and t1 == 'c':
+            jinc = 1
+        elif t0 == 'c' and t1 in ('{', '}', 'v', ']'):
+            jinc = 1
+        elif t0 == 'c' and t1 == 'c':
+            jinc = 2
+        else:
+            jinc = 4
+        jincs.append(jinc)
+        n += jinc
+    return n, jincs
+
+thscale, k0scale, k1scale, k2scale = 1, 1, 1, 1
+
+def inversedot_woodbury(m, v):
+    a = zeros((n, 11), Float)
+    for i in range(n):
+        for j in range(max(-7, -i), min(4, n - i)):
+            a[i, j + 7] = m[i, i + j]
+    print a
+    al, indx, d = band.bandec(a, 7, 3)
+    VtZ = identity(4, Float)
+    Z = zeros((n, 4), Float)
+    for i in range(4):
+        u = zeros(n, Float)
+        for j in range(4):
+            u[j] = m[j, n - 4 + i]
+        band.banbks(a, 7, 3, al, indx, u)
+        for k in range(n):
+            Z[k, i] = u[k]
+        #Z[:,i] = u
+        for j in range(4):
+            VtZ[j, i] += u[n - 4 + j]
+    print Z
+    print VtZ
+    H = la.inverse(VtZ)
+    print H
+    band.banbks(a, 7, 3, al, indx, v)
+    return(v - dot(Z, dot(H, v[n - 4:])))
+
+def inversedot(m, v):
+    return dot(la.inverse(m), v)
+    n, nn = m.shape
+    if 1:
+        for i in range(n):
+            sys.stdout.write('% ')
+            for j in range(n):
+                if m[i, j] > 0: sys.stdout.write('+ ')
+                elif m[i, j] < 0: sys.stdout.write('- ')
+                else: sys.stdout.write('  ')
+            sys.stdout.write('\n')
+
+    cyclic = False
+    for i in range(4):
+        for j in range(n - 4, n):
+            if m[i, j] != 0:
+                cyclic = True
+    print '% cyclic:', cyclic
+    if not cyclic:
+        a = zeros((n, 11), Float)
+        for i in range(n):
+            for j in range(max(-5, -i), min(6, n - i)):
+                a[i, j + 5] = m[i, i + j]
+        for i in range(n):
+            sys.stdout.write('% ')
+            for j in range(11):
+                if a[i, j] > 0: sys.stdout.write('+ ')
+                elif a[i, j] < 0: sys.stdout.write('- ')
+                else: sys.stdout.write('  ')
+            sys.stdout.write('\n')
+        al, indx, d = band.bandec(a, 5, 5)
+        print a
+        band.banbks(a, 5, 5, al, indx, v)
+        return v
+    else:
+        #return inversedot_woodbury(m, v)
+        bign = 3 * n
+        a = zeros((bign, 11), Float)
+        u = zeros(bign, Float)
+        for i in range(bign):
+            u[i] = v[i % n]
+            for j in range(-7, 4):
+                a[i, j + 7] = m[i % n, (i + j + 7 * n) % n]
+        #print a
+        if 1:
+            for i in range(bign):
+                sys.stdout.write('% ')
+                for j in range(11):
+                    if a[i, j] > 0: sys.stdout.write('+ ')
+                    elif a[i, j] < 0: sys.stdout.write('- ')
+                    else: sys.stdout.write('  ')
+                sys.stdout.write('\n')
+        #print u
+        al, indx, d = band.bandec(a, 5, 5)
+        band.banbks(a, 5, 5, al, indx, u)
+        #print u
+        return u[n + 2: 2 * n + 2]
+
+def iter(segs, nodes):
+    n, jincs = count_vec(nodes)
+    print '%', jincs
+    v = zeros(n, Float)
+    m = zeros((n, n), Float)
+    for i in range(len(segs)):
+        segs[i].set_ends_from_ks()
+    j = 0
+    j0 = 0
+    for i in range(len(segs)):
+        i1 = (i + 1) % len(nodes)
+        t0 = nodes[i].ty
+        t1 = nodes[i1].ty
+        seg = segs[i]
+
+        derivs = seg.compute_pderivs()
+        print '%derivs:', derivs
+
+        jinc = jincs[i]   # the number of params on this seg
+        print '%', t0, t1, jinc, j0
+
+        # The constraints are laid out as follows:
+        #   constraints that cross the node on the left
+        #   constraints on the left side
+        #   constraints on the right side
+        #   constraints that cross the node on the right
+
+        jj = j0    # the index into the constraint row we're writing
+        jthl, jk0l, jk1l, jk2l = -1, -1, -1, -1
+        jthr, jk0r, jk1r, jk2r = -1, -1, -1, -1
+
+        # constraints crossing left
+
+        if t0 == 'o':
+            jthl = jj + 0
+            jk0l = jj + 1
+            jk1l = jj + 2
+            jk2l = jj + 3
+            jj += 4
+        elif t0 in ('c', '[', ']'):
+            jthl = jj + 0
+            jk0l = jj + 1
+            jj += 2
+
+        # constraints on left
+
+        if t0 in ('[', 'v', '{') and jinc == 4:
+            jk1l = jj
+            jj += 1
+        if t0 in ('[', 'v', '{', 'c') and jinc == 4:
+            jk2l = jj
+            jj += 1
+
+        # constraints on right
+
+        if t1 in (']', 'v', '}') and jinc == 4:
+            jk1r = jj
+            jj += 1
+        if t1 in (']', 'v', '}', 'c') and jinc == 4:
+            jk2r = jj
+            jj += 1
+
+        # constraints crossing right
+
+        jj %= n
+        j1 = jj
+
+        if t1 == 'o':
+            jthr = jj + 0
+            jk0r = jj + 1
+            jk1r = jj + 2
+            jk2r = jj + 3
+            jj += 4
+        elif t1 in ('c', '[', ']'):
+            jthr = jj + 0
+            jk0r = jj + 1
+            jj += 2
+
+        print '%', jthl, jk0l, jk1l, jk2l, jthr, jk0r, jk1r, jk2r
+
+        if jthl >= 0:
+            v[jthl] += thscale * (nodes[i].th - seg.endl[0])
+            if jinc == 1:
+                m[jthl][j] += derivs[0][0][0]
+            elif jinc == 2:
+                m[jthl][j + 1] += derivs[0][0][0]
+                m[jthl][j] += derivs[1][0][0]
+            elif jinc == 4:
+                m[jthl][j + 2] += derivs[0][0][0]
+                m[jthl][j + 3] += derivs[1][0][0]
+                m[jthl][j + 0] += derivs[2][0][0]
+                m[jthl][j + 1] += derivs[3][0][0]
+        if jk0l >= 0:
+            v[jk0l] += k0scale * seg.endl[1]
+            if jinc == 1:
+                m[jk0l][j] -= derivs[0][0][1]
+            elif jinc == 2:
+                m[jk0l][j + 1] -= derivs[0][0][1]
+                m[jk0l][j] -= derivs[1][0][1]
+            elif jinc == 4:
+                m[jk0l][j + 2] -= derivs[0][0][1]
+                m[jk0l][j + 3] -= derivs[1][0][1]
+                m[jk0l][j + 0] -= derivs[2][0][1]
+                m[jk0l][j + 1] -= derivs[3][0][1]
+        if jk1l >= 0:
+            v[jk1l] += k1scale * seg.endl[2]
+            m[jk1l][j + 2] -= derivs[0][0][2]
+            m[jk1l][j + 3] -= derivs[1][0][2]
+            m[jk1l][j + 0] -= derivs[2][0][2]
+            m[jk1l][j + 1] -= derivs[3][0][2]
+        if jk2l >= 0:
+            v[jk2l] += k2scale * seg.endl[3]
+            m[jk2l][j + 2] -= derivs[0][0][3]
+            m[jk2l][j + 3] -= derivs[1][0][3]
+            m[jk2l][j + 0] -= derivs[2][0][3]
+            m[jk2l][j + 1] -= derivs[3][0][3]
+
+        if jthr >= 0:
+            v[jthr] -= thscale * seg.endr[0]
+            if jinc == 1:
+                m[jthr][j] += derivs[0][1][0]
+            elif jinc == 2:
+                m[jthr][j + 1] += derivs[0][1][0]
+                m[jthr][j + 0] += derivs[1][1][0]
+            elif jinc == 4:
+                m[jthr][j + 2] += derivs[0][1][0]
+                m[jthr][j + 3] += derivs[1][1][0]
+                m[jthr][j + 0] += derivs[2][1][0]
+                m[jthr][j + 1] += derivs[3][1][0]
+        if jk0r >= 0:
+            v[jk0r] -= k0scale * seg.endr[1]
+            if jinc == 1:
+                m[jk0r][j] += derivs[0][1][1]
+            elif jinc == 2:
+                m[jk0r][j + 1] += derivs[0][1][1]
+                m[jk0r][j + 0] += derivs[1][1][1]
+            elif jinc == 4:
+                m[jk0r][j + 2] += derivs[0][1][1]
+                m[jk0r][j + 3] += derivs[1][1][1]
+                m[jk0r][j + 0] += derivs[2][1][1]
+                m[jk0r][j + 1] += derivs[3][1][1]
+        if jk1r >= 0:
+            v[jk1r] -= k1scale * seg.endr[2]
+            m[jk1r][j + 2] += derivs[0][1][2]
+            m[jk1r][j + 3] += derivs[1][1][2]
+            m[jk1r][j + 0] += derivs[2][1][2]
+            m[jk1r][j + 1] += derivs[3][1][2]
+        if jk2r >= 0:
+            v[jk2r] -= k2scale * seg.endr[3]
+            m[jk2r][j + 2] += derivs[0][1][3]
+            m[jk2r][j + 3] += derivs[1][1][3]
+            m[jk2r][j + 0] += derivs[2][1][3]
+            m[jk2r][j + 1] += derivs[3][1][3]
+
+        j += jinc
+        j0 = j1
+    #print m
+    dk = inversedot(m, v)
+    dkmul = 1
+    j = 0
+    for i in range(len(segs)):
+        jinc = jincs[i]
+        if jinc == 1:
+            segs[i].ks[0] += dkmul * dk[j]
+        elif jinc == 2:
+            segs[i].ks[0] += dkmul * dk[j + 1]
+            segs[i].ks[1] += dkmul * dk[j + 0]
+        elif jinc == 4:
+            segs[i].ks[0] += dkmul * dk[j + 2]
+            segs[i].ks[1] += dkmul * dk[j + 3]
+            segs[i].ks[2] += dkmul * dk[j + 0]
+            segs[i].ks[3] += dkmul * dk[j + 1]
+        j += jinc
+
+    norm = 0.
+    for i in range(len(dk)):
+        norm += dk[i] * dk[i]
+    return sqrt(norm)
+
+
+def plot_path(segs, nodes, tol = 1.0, show_cpts = False):
+    if show_cpts:
+        cpts = []
+    j = 0
+    cmd = 'moveto'
+    for i in range(len(segs)):
+        i1 = (i + 1) % len(nodes)
+        n0 = nodes[i]
+        n1 = nodes[i1]
+        x0, y0, t0 = n0.x, n0.y, n0.ty
+        x1, y1, t1 = n1.x, n1.y, n1.ty
+        ks = segs[i].ks
+        abs_ks = abs(ks[0]) + abs(ks[1] / 2) + abs(ks[2] / 8) + abs(ks[3] / 48)
+        n_subdiv = int(ceil(.001 + tol * abs_ks))
+        n_subhalf = (n_subdiv + 1) / 2
+        if n_subdiv > 1:
+            n_subdiv = n_subhalf * 2
+        ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625)
+        pside = poly3.int_3spiro_poly(ksp, n_subhalf)
+        ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625)
+        mside = poly3.int_3spiro_poly(ksm, n_subhalf)
+        mside.reverse()
+        for j in range(len(mside)):
+            mside[j] = (-mside[j][0], -mside[j][1])
+        if n_subdiv > 1:
+            pts = mside + pside[1:]
+        else:
+            pts = mside[:1] + pside[1:]
+        chord_th = atan2(y1 - y0, x1 - x0)
+        chord_len = hypot(y1 - y0, x1 - x0)
+        rot = chord_th - atan2(pts[-1][1] - pts[0][1], pts[-1][0] - pts[0][0])
+        scale = chord_len / hypot(pts[-1][1] - pts[0][1], pts[-1][0] - pts[0][0])
+        u, v = scale * cos(rot), scale * sin(rot)
+        xt = x0 - u * pts[0][0] + v * pts[0][1]
+        yt = y0 - u * pts[0][1] - v * pts[0][0]
+        s = -.5
+        for x, y in pts:
+            xp, yp = xt + u * x - v * y, yt + u * y + v * x
+            thp = (((ks[3]/24 * s + ks[2]/6) * s + ks[1] / 2) * s + ks[0]) * s + rot
+            up, vp = scale / (1.5 * n_subdiv) * cos(thp), scale / (1.5 * n_subdiv) * sin(thp)
+            if s == -.5:
+                if cmd == 'moveto':
+                    print xp, yp, 'moveto'
+                    cmd = 'curveto'
+            else:
+                if show_cpts:
+                    cpts.append((xlast + ulast, ylast + vlast))
+                    cpts.append((xp - up, yp - vp))
+                print xlast + ulast, ylast + vlast, xp - up, yp - vp, xp, yp, 'curveto'
+            xlast, ylast, ulast, vlast = xp, yp, up, vp
+            s += 1. / n_subdiv
+        if t1 == 'v':
+            j += 2
+        else:
+            j += 1
+    print 'stroke'
+    if show_cpts:
+        for x, y in cpts:
+            print 'gsave 0 0 1 setrgbcolor', x, y, 'translate circle fill grestore'
+
+def plot_ks(segs, nodes, xo, yo, xscale, yscale):
+    j = 0
+    cmd = 'moveto'
+    x = xo
+    for i in range(len(segs)):
+        i1 = (i + 1) % len(nodes)
+        n0 = nodes[i]
+        n1 = nodes[i1]
+        x0, y0, t0 = n0.x, n0.y, n0.ty
+        x1, y1, t1 = n1.x, n1.y, n1.ty
+        ks = segs[i].ks
+        chord, ch_th = poly3.integ_chord(ks)
+        l = chord/segs[i].chord
+        k0 = l * poly3.eval_cubic(ks[0], ks[1], .5 * ks[2], 1./6 * ks[3], -.5)
+        print x, yo + yscale * k0, cmd
+        cmd = 'lineto'
+        k3 = l * poly3.eval_cubic(ks[0], ks[1], .5 * ks[2], 1./6 * ks[3], .5)
+        k1 = k0 + l/3 * (ks[1] - 0.5 * ks[2] + .125 * ks[3])
+        k2 = k3 - l/3 * (ks[1] + 0.5 * ks[2] + .125 * ks[3])
+        print x + xscale / l / 3., yo + yscale * k1
+        print x + 2 * xscale / l / 3., yo + yscale * k2
+        print x + xscale / l, yo + yscale * k3, 'curveto'
+        x += xscale / l
+        if t1 == 'v':
+            j += 2
+        else:
+            j += 1
+    print 'stroke'
+    print xo, yo, 'moveto', x, yo, 'lineto stroke'
+
+def plot_nodes(nodes, segs):
+    for i in range(len(nodes)):
+        n = nodes[i]
+        print 'gsave', n.x, n.y, 'translate'
+        if n.ty in ('c', '[', ']'):
+            th = segs[i].th - segs[i].endl[0]
+            if n.ty == ']': th += pi
+            print 180 * th / pi, 'rotate'
+        if n.ty == 'o':
+            print 'circle fill'
+        elif n.ty == 'c':
+            print '3 4 poly fill'
+        elif n.ty in ('v', '{', '}'):
+            print '45 rotate 3 4 poly fill'
+        elif n.ty in ('[', ']'):
+            print '0 -3 moveto 0 0 3 90 270 arc fill'
+        else:
+            print '5 3 poly fill'
+        print 'grestore'
+
+def prologue():
+    print '/ss 2 def'
+    print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def'
+    print '/poly {'
+    print '    dup false exch {'
+    print '        0 3 index 2 index { lineto } { moveto } ifelse pop true'
+    print '        360.0 2 index div rotate'
+    print '    } repeat pop pop pop'
+    print '} bind def'
+
+def run_path(path, show_iter = False, n = 10, xo = 36, yo = 550, xscale = .25, yscale = 2000, pl_nodes = True):
+    segs, nodes = setup_path(path)
+    print '.5 setlinewidth'
+    for i in range(n):
+        if i == n - 1:
+            print '0 0 0 setrgbcolor 1 setlinewidth'
+        elif i == 0:
+            print '1 0 0 setrgbcolor'
+        elif i == 1:
+            print '0 0.5 0 setrgbcolor'
+        elif i == 2:
+            print '0.3 0.3 0.3 setrgbcolor'
+        norm = iter(segs, nodes)
+        print '% norm =', norm
+        if show_iter or i == n - 1:
+            #print '1 0 0 setrgbcolor'
+            #plot_path(segs, nodes, tol = 5)
+            #print '0 0 0 setrgbcolor'
+            plot_path(segs, nodes, tol = 1.0)
+            plot_ks(segs, nodes, xo, yo, xscale, yscale)
+    if pl_nodes:
+        plot_nodes(nodes, segs)
+
+if __name__ == '__main__':
+    if 1:
+        path = [(100, 350, 'o'), (225, 350, 'o'), (350, 450, 'o'),
+                (450, 400, 'o'), (315, 205, 'o'), (300, 200, 'o'),
+                (285, 205, 'o')]
+
+    if 1:
+        path = [(100, 350, 'o'), (175, 375, '['), (250, 375, ']'), (325, 425, '['),
+                (325, 450, ']'),
+                (400, 475, 'o'), (350, 200, 'c')]
+
+    if 0:
+        ecc, ty, ty1 = 0.56199, 'c', 'c'
+        ecc, ty, ty1 = 0.49076, 'o', 'o',
+        ecc, ty, ty1 = 0.42637, 'o', 'c'
+        path = [(300 - 200 * ecc, 300, ty), (300, 100, ty1),
+                (300 + 200 * ecc, 300, ty), (300, 500, ty1)]
+
+    # difficult path #3
+    if 0:
+        path = [(100, 300, '{'), (225, 350, 'o'), (350, 500, 'o'),
+                (450, 500, 'o'), (450, 450, 'o'), (300, 200, '}')]
+
+    if 0:
+        path = [(100, 100, '{'), (200, 180, 'v'), (250, 215, 'o'),
+                (300, 200, 'o'), (400, 100, '}')]
+
+    if 0:
+        praw = [
+        (134, 90, 'o'),
+        (192, 68, 'o'),
+        (246, 66, 'o'),
+        (307, 107, 'o'),
+        (314, 154, '['),
+        (317, 323, ']'),
+        (347, 389, 'o'),
+        (373, 379, 'v'),
+        (386, 391, 'v'),
+        (365, 409, 'o'),
+        (335, 419, 'o'),
+        (273, 390, 'v'),
+        (251, 405, 'o'),
+        (203, 423, 'o'),
+        (102, 387, 'o'),
+        (94, 321, 'o'),
+        (143, 276, 'o'),
+        (230, 251, 'o'),
+        (260, 250, 'v'),
+        (260, 220, '['),
+        (258, 157, ']'),
+        (243, 110, 'o'),
+        (159, 99, 'o'),
+        (141, 121, 'o'),
+        (156, 158, 'o'),
+        (121, 184, 'o'),
+        (106, 117, 'o')]
+        if 0:
+            praw = [
+            (275, 56, 'o'),
+            (291, 75, 'v'),
+            (312, 61, 'o'),
+            (344, 50, 'o'),
+            (373, 72, 'o'),
+            (356, 91, 'o'),
+            (334, 81, 'o'),
+            (297, 85, 'v'),
+            (306, 116, 'o'),
+            (287, 180, 'o'),
+            (236, 213, 'o'),
+            (182, 212, 'o'),
+            (157, 201, 'v'),
+            (149, 209, 'o'),
+            (143, 230, 'o'),
+            (162, 246, 'c'),
+            (202, 252, 'o'),
+            (299, 260, 'o'),
+            (331, 282, 'o'),
+            (341, 341, 'o'),
+            (308, 390, 'o'),
+            (258, 417, 'o'),
+            (185, 422, 'o'),
+            (106, 377, 'o'),
+            (110, 325, 'o'),
+            (133, 296, 'o'),
+            (147, 283, 'v'),
+            (117, 238, 'o'),
+            (133, 205, 'o'),
+            (147, 191, 'v'),
+            (126, 159, 'o'),
+            (128, 94, 'o'),
+            (167, 50, 'o'),
+            (237, 39, 'o')]
+            
+        path = []
+        for x, y, ty in praw:
+            #if ty == 'o': ty = 'c'
+            path.append((x, 550 - y, ty))
+
+    if 0:
+        path = [(100, 300, 'o'), (300, 100, 'o'), (300, 500, 'o')]
+
+    if 0:
+        # Woodford data set
+        ty = 'o'
+        praw = [(0, 0, '{'), (1, 1.9, ty), (2, 2.7, ty), (3, 2.6, ty),
+                (4, 1.6, ty), (5, .89, ty), (6, 1.2, '}')]
+        path = []
+        for x, y, t in praw:
+            path.append((100 + 80 * x, 100 + 80 * y, t))
+
+    if 0:
+        ycen = 32
+        yrise = 0
+        path = []
+        ty = '{'
+        for i in range(10):
+            path.append((50 + i * 30, 250 + (10 - i) * yrise, ty))
+            ty = 'o'
+        path.append((350, 250 + ycen, ty))
+        for i in range(1, 10):
+            path.append((350 + i * 30, 250 + i * yrise, ty))
+        path.append((650, 250 + 10 * yrise, '}'))
+
+    prologue()
+
+    run_path(path, show_iter = True, n=5)

+ 2 - 0
third_party/spiro/curves/cloth_off.py

@@ -0,0 +1,2 @@
+# Fancy new algorithms for computing the offset of a clothoid.
+

+ 66 - 0
third_party/spiro/curves/clothoid.py

@@ -0,0 +1,66 @@
+from math import *
+import cornu
+
+def mod_2pi(th):
+    u = th / (2 * pi)
+    return 2 * pi * (u - floor(u + 0.5))
+
+# Given clothoid k(s) = k0 + k1 s, compute th1 - th0 of chord from s = -.5
+# to .5.
+def compute_dth(k0, k1):
+    if k1 < 0:
+        return -compute_dth(k0, -k1)
+    elif k1 == 0:
+        return 0
+    sqrk1 = sqrt(2 * k1)
+    t0 = (k0 - .5 * k1) / sqrk1
+    t1 = (k0 + .5 * k1) / sqrk1
+    (y0, x0) = cornu.eval_cornu(t0)
+    (y1, x1) = cornu.eval_cornu(t1)
+    chord_th = atan2(y1 - y0, x1 - x0)
+    return mod_2pi(t1 * t1 - chord_th) - mod_2pi(chord_th - t0 * t0)
+
+def compute_chord(k0, k1):
+    if k1 == 0:
+        if k0 == 0:
+            return 1
+        else:
+            return sin(k0 * .5) / (k0 * .5)
+    sqrk1 = sqrt(2 * abs(k1))
+    t0 = (k0 - .5 * k1) / sqrk1
+    t1 = (k0 + .5 * k1) / sqrk1
+    (y0, x0) = cornu.eval_cornu(t0)
+    (y1, x1) = cornu.eval_cornu(t1)
+    return hypot(y1 - y0, x1 - x0) / abs(t1 - t0)
+
+# Given th0 and th1 at endpoints (measured from chord), return k0
+# and k1 such that the clothoid k(s) = k0 + k1 s, evaluated from
+# s = -.5 to .5, has the tangents given
+def solve_clothoid(th0, th1, verbose = False):
+    k0 = th0 + th1
+
+    # initial guess
+    k1 = 6 * (th1 - th0)
+    error = (th1 - th0) - compute_dth(k0, k1)
+    if verbose:
+        print k0, k1, error
+
+    k1_old, error_old = k1, error
+    # second guess based on d(dth)/dk1 ~ 1/6
+    k1 += 6 * error
+    error = (th1 - th0) - compute_dth(k0, k1)
+    if verbose:
+        print k0, k1, error
+
+    # secant method
+    for i in range(10):
+        if abs(error) < 1e-9: break
+        k1_old, error_old, k1 = k1, error, k1 + (k1_old - k1) * error / (error - error_old)
+        error = (th1 - th0) - compute_dth(k0, k1)
+        if verbose:
+            print k0, k1, error
+
+    return k0, k1
+
+if __name__ == '__main__':
+    print solve_clothoid(.06, .05, True)

+ 140 - 0
third_party/spiro/curves/cornu.py

@@ -0,0 +1,140 @@
+from math import *
+
+# implementation adapted from cephes
+
+def polevl(x, coef):
+    ans = coef[-1]
+    for i in range(len(coef) - 2, -1, -1):
+        ans = ans * x + coef[i]
+    return ans
+
+sn = [
+-2.99181919401019853726E3,
+ 7.08840045257738576863E5,
+-6.29741486205862506537E7,
+ 2.54890880573376359104E9,
+-4.42979518059697779103E10,
+ 3.18016297876567817986E11
+]
+sn.reverse()
+sd = [
+ 1.00000000000000000000E0,
+ 2.81376268889994315696E2,
+ 4.55847810806532581675E4,
+ 5.17343888770096400730E6,
+ 4.19320245898111231129E8,
+ 2.24411795645340920940E10,
+ 6.07366389490084639049E11
+]
+sd.reverse()
+cn = [
+-4.98843114573573548651E-8,
+ 9.50428062829859605134E-6,
+-6.45191435683965050962E-4,
+ 1.88843319396703850064E-2,
+-2.05525900955013891793E-1,
+ 9.99999999999999998822E-1
+]
+cn.reverse()
+cd = [
+ 3.99982968972495980367E-12,
+ 9.15439215774657478799E-10,
+ 1.25001862479598821474E-7,
+ 1.22262789024179030997E-5,
+ 8.68029542941784300606E-4,
+ 4.12142090722199792936E-2,
+ 1.00000000000000000118E0
+]
+cd.reverse()
+
+fn = [
+  4.21543555043677546506E-1,
+  1.43407919780758885261E-1,
+  1.15220955073585758835E-2,
+  3.45017939782574027900E-4,
+  4.63613749287867322088E-6,
+  3.05568983790257605827E-8,
+  1.02304514164907233465E-10,
+  1.72010743268161828879E-13,
+  1.34283276233062758925E-16,
+  3.76329711269987889006E-20
+]
+fn.reverse()
+fd = [
+  1.00000000000000000000E0,
+  7.51586398353378947175E-1,
+  1.16888925859191382142E-1,
+  6.44051526508858611005E-3,
+  1.55934409164153020873E-4,
+  1.84627567348930545870E-6,
+  1.12699224763999035261E-8,
+  3.60140029589371370404E-11,
+  5.88754533621578410010E-14,
+  4.52001434074129701496E-17,
+  1.25443237090011264384E-20
+]
+fd.reverse()
+gn = [
+  5.04442073643383265887E-1,
+  1.97102833525523411709E-1,
+  1.87648584092575249293E-2,
+  6.84079380915393090172E-4,
+  1.15138826111884280931E-5,
+  9.82852443688422223854E-8,
+  4.45344415861750144738E-10,
+  1.08268041139020870318E-12,
+  1.37555460633261799868E-15,
+  8.36354435630677421531E-19,
+  1.86958710162783235106E-22
+]
+gn.reverse()
+gd = [
+  1.00000000000000000000E0,
+  1.47495759925128324529E0,
+  3.37748989120019970451E-1,
+  2.53603741420338795122E-2,
+  8.14679107184306179049E-4,
+  1.27545075667729118702E-5,
+  1.04314589657571990585E-7,
+  4.60680728146520428211E-10,
+  1.10273215066240270757E-12,
+  1.38796531259578871258E-15,
+  8.39158816283118707363E-19,
+  1.86958710162783236342E-22
+]
+gd.reverse()
+
+
+def fresnel(xxa):
+    x = abs(xxa)
+    x2 = x * x
+    if x2 < 2.5625:
+        t = x2 * x2
+        ss = x * x2 * polevl(t, sn) / polevl(t, sd)
+        cc = x * polevl(t, cn) / polevl(t, cd)
+    elif x > 36974.0:
+        ss = 0.5
+        cc = 0.5
+    else:
+        t = pi * x2
+        u = 1.0 / (t * t)
+        t = 1.0 / t
+        f = 1.0 - u * polevl(u, fn) / polevl(u, fd)
+        g = t * polevl(u, gn) / polevl(u, gd)
+        t = pi * .5 * x2
+        c = cos(t)
+        s = sin(t)
+        t = pi * x
+        cc = 0.5 + (f * s - g * c) / t
+        ss = 0.5 - (f * c + g * s) / t
+    if xxa < 0:
+        cc = -cc
+        ss = -ss
+    return ss, cc
+
+def eval_cornu(t):
+    spio2 = sqrt(pi * .5)
+    s, c = fresnel(t / spio2)
+    s *= spio2
+    c *= spio2
+    return s, c

+ 29 - 0
third_party/spiro/curves/euler-elastica.py

@@ -0,0 +1,29 @@
+from math import *
+
+def plot_elastica(a, c):
+    s = 500
+    cmd = 'moveto'
+    dx = .001
+    x, y = 0, 0
+    if c * c > 2 * a * a:
+        g = sqrt(c * c - 2 * a * a)
+        x = g + .01
+    if c == 0:
+        x = .001
+    try:
+        for i in range(1000):
+            print 6 + s * x, 200 + s * y, cmd
+            cmd = 'lineto'
+            x += dx
+            if 1 and c * c > 2 * a * a:
+                print (c * c - x * x) * (x * x - g * g)
+                dy = dx * (x * x - .5 * c * c - .5 * g * g) / sqrt((c * c - x * x) * (x * x - g * g))
+            else:
+                dy = dx * (a * a - c * c + x * x)/sqrt((c * c - x * x) * (2 * a * a - c * c + x * x))
+            y += dy
+    except ValueError, e:
+        pass
+    print 'stroke'
+
+plot_elastica(1, 0)
+print 'showpage'

Some files were not shown because too many files changed in this diff