summaryrefslogtreecommitdiff
path: root/third_party
diff options
context:
space:
mode:
authorRoozbeh Pournader <roozbeh@google.com>2014-07-26 11:20:29 -0700
committerRoozbeh Pournader <roozbeh@google.com>2014-07-26 11:20:29 -0700
commit0f1bd27678fb542f8601bbaf493baa1c22732fe2 (patch)
tree78ecb8f8082fb1b3ae24720549578b9272f36f0c /third_party
parent5a480b939b43dec473550292501a88518fe1208c (diff)
Add Spiro version 0.01.
Diffstat (limited to 'third_party')
-rw-r--r--third_party/spiro/LICENSE339
-rw-r--r--third_party/spiro/README24
-rw-r--r--third_party/spiro/README.third_party10
-rw-r--r--third_party/spiro/curves/band.py85
-rw-r--r--third_party/spiro/curves/bezfigs.py183
-rw-r--r--third_party/spiro/curves/bigmat.py662
-rw-r--r--third_party/spiro/curves/cloth_off.py2
-rw-r--r--third_party/spiro/curves/clothoid.py66
-rw-r--r--third_party/spiro/curves/cornu.py140
-rw-r--r--third_party/spiro/curves/euler-elastica.py29
-rw-r--r--third_party/spiro/curves/fromcubic.py208
-rw-r--r--third_party/spiro/curves/mecsolve.py859
-rw-r--r--third_party/spiro/curves/mvc.py208
-rw-r--r--third_party/spiro/curves/numintsynth.py176
-rw-r--r--third_party/spiro/curves/offset.py21
-rw-r--r--third_party/spiro/curves/pcorn.py160
-rw-r--r--third_party/spiro/curves/plot_solve_clothoid.py164
-rw-r--r--third_party/spiro/curves/poly3.py148
-rw-r--r--third_party/spiro/curves/polymat-bad.py64
-rw-r--r--third_party/spiro/curves/polymat.py399
-rw-r--r--third_party/spiro/curves/tocubic.py461
-rw-r--r--third_party/spiro/font/Makefile3
-rw-r--r--third_party/spiro/font/blend.c372
-rw-r--r--third_party/spiro/font/cut.py52
-rw-r--r--third_party/spiro/font/mkblends.py16
-rw-r--r--third_party/spiro/font/replace_class.py14
-rw-r--r--third_party/spiro/font/segment.c194
-rw-r--r--third_party/spiro/ppedit/Makefile25
-rw-r--r--third_party/spiro/ppedit/Makefile_gtk15
-rw-r--r--third_party/spiro/ppedit/README112
-rw-r--r--third_party/spiro/ppedit/bezctx.c48
-rw-r--r--third_party/spiro/ppedit/bezctx.h10
-rw-r--r--third_party/spiro/ppedit/bezctx_hittest.c261
-rw-r--r--third_party/spiro/ppedit/bezctx_hittest.h5
-rw-r--r--third_party/spiro/ppedit/bezctx_intf.h20
-rw-r--r--third_party/spiro/ppedit/bezctx_libart.c131
-rw-r--r--third_party/spiro/ppedit/bezctx_libart.h5
-rw-r--r--third_party/spiro/ppedit/bezctx_ps.c116
-rw-r--r--third_party/spiro/ppedit/bezctx_ps.h8
-rw-r--r--third_party/spiro/ppedit/bezctx_quartz.c78
-rw-r--r--third_party/spiro/ppedit/bezctx_quartz.h4
-rw-r--r--third_party/spiro/ppedit/bezctx_x3.c96
-rw-r--r--third_party/spiro/ppedit/bezctx_x3.h3
-rw-r--r--third_party/spiro/ppedit/carbon_main.c182
-rw-r--r--third_party/spiro/ppedit/cornu.c615
-rw-r--r--third_party/spiro/ppedit/cornu.h13
-rw-r--r--third_party/spiro/ppedit/gpl.txt339
-rw-r--r--third_party/spiro/ppedit/image.c141
-rw-r--r--third_party/spiro/ppedit/image.h11
-rw-r--r--third_party/spiro/ppedit/pe_view.c548
-rw-r--r--third_party/spiro/ppedit/pe_view.h11
-rw-r--r--third_party/spiro/ppedit/plate.c526
-rw-r--r--third_party/spiro/ppedit/plate.h100
-rw-r--r--third_party/spiro/ppedit/ppedit.c603
-rw-r--r--third_party/spiro/ppedit/ppedit_gtk1.c930
-rw-r--r--third_party/spiro/ppedit/sexp.c127
-rw-r--r--third_party/spiro/ppedit/sexp.h10
-rw-r--r--third_party/spiro/ppedit/spiro.c1070
-rw-r--r--third_party/spiro/ppedit/spiro.h18
-rw-r--r--third_party/spiro/ppedit/zmisc.h12
-rw-r--r--third_party/spiro/x3/Makefile25
-rw-r--r--third_party/spiro/x3/pyrex/Makefile39
-rw-r--r--third_party/spiro/x3/pyrex/beztest.py44
-rw-r--r--third_party/spiro/x3/pyrex/main.py40
-rw-r--r--third_party/spiro/x3/pyrex/x3.pyx259
-rw-r--r--third_party/spiro/x3/test.c158
-rw-r--r--third_party/spiro/x3/x3.h293
-rw-r--r--third_party/spiro/x3/x3carbon.c1002
-rw-r--r--third_party/spiro/x3/x3common.c359
-rw-r--r--third_party/spiro/x3/x3common.h16
-rw-r--r--third_party/spiro/x3/x3gtk.c778
-rw-r--r--third_party/spiro/x3/x3win32.c174
72 files changed, 14429 insertions, 0 deletions
diff --git a/third_party/spiro/LICENSE b/third_party/spiro/LICENSE
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/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.
diff --git a/third_party/spiro/README b/third_party/spiro/README
new file mode 100644
index 0000000..e6a2d4b
--- /dev/null
+++ b/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.
diff --git a/third_party/spiro/README.third_party b/third_party/spiro/README.third_party
new file mode 100644
index 0000000..6c59b1a
--- /dev/null
+++ b/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. \ No newline at end of file
diff --git a/third_party/spiro/curves/band.py b/third_party/spiro/curves/band.py
new file mode 100644
index 0000000..7e61d35
--- /dev/null
+++ b/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
diff --git a/third_party/spiro/curves/bezfigs.py b/third_party/spiro/curves/bezfigs.py
new file mode 100644
index 0000000..b7e408b
--- /dev/null
+++ b/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)
diff --git a/third_party/spiro/curves/bigmat.py b/third_party/spiro/curves/bigmat.py
new file mode 100644
index 0000000..c1fac0c
--- /dev/null
+++ b/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)
diff --git a/third_party/spiro/curves/cloth_off.py b/third_party/spiro/curves/cloth_off.py
new file mode 100644
index 0000000..0ebaf4d
--- /dev/null
+++ b/third_party/spiro/curves/cloth_off.py
@@ -0,0 +1,2 @@
+# Fancy new algorithms for computing the offset of a clothoid.
+
diff --git a/third_party/spiro/curves/clothoid.py b/third_party/spiro/curves/clothoid.py
new file mode 100644
index 0000000..b3ae6af
--- /dev/null
+++ b/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)
diff --git a/third_party/spiro/curves/cornu.py b/third_party/spiro/curves/cornu.py
new file mode 100644
index 0000000..26f68e9
--- /dev/null
+++ b/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
diff --git a/third_party/spiro/curves/euler-elastica.py b/third_party/spiro/curves/euler-elastica.py
new file mode 100644
index 0000000..a721d1e
--- /dev/null
+++ b/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'
diff --git a/third_party/spiro/curves/fromcubic.py b/third_party/spiro/curves/fromcubic.py
new file mode 100644
index 0000000..210f9d4
--- /dev/null
+++ b/third_party/spiro/curves/fromcubic.py
@@ -0,0 +1,208 @@
+# Convert piecewise cubic into piecewise clothoid representation.
+
+from math import *
+
+import clothoid
+import pcorn
+import tocubic
+
+import offset
+
+def read_bz(f):
+ result = []
+ for l in f.xreadlines():
+ s = l.split()
+ if len(s) > 0:
+ cmd = s[-1]
+ #print s[:-1], cmd
+ if cmd == 'm':
+ sp = []
+ result.append(sp)
+ curpt = [float(x) for x in s[0:2]]
+ startpt = curpt
+ elif cmd == 'l':
+ newpt = [float(x) for x in s[0:2]]
+ sp.append((curpt, newpt))
+ curpt = newpt
+ elif cmd == 'c':
+ c1 = [float(x) for x in s[0:2]]
+ c2 = [float(x) for x in s[2:4]]
+ newpt = [float(x) for x in s[4:6]]
+ sp.append((curpt, c1, c2, newpt))
+ curpt = newpt
+ return result
+
+def plot_bzs(bzs, z0, scale, fancy = False):
+ for sp in bzs:
+ for i in range(len(sp)):
+ bz = sp[i]
+ tocubic.plot_bz(bz, z0, scale, i == 0)
+ print 'stroke'
+ if fancy:
+ for i in range(len(sp)):
+ bz = sp[i]
+
+ x0, y0 = z0[0] + scale * bz[0][0], z0[1] + scale * bz[0][1]
+ print 'gsave', x0, y0, 'translate circle fill grestore'
+ if len(bz) == 4:
+ x1, y1 = z0[0] + scale * bz[1][0], z0[1] + scale * bz[1][1]
+ x2, y2 = z0[0] + scale * bz[2][0], z0[1] + scale * bz[2][1]
+ x3, y3 = z0[0] + scale * bz[3][0], z0[1] + scale * bz[3][1]
+ print 'gsave 0.5 setlinewidth', x0, y0, 'moveto'
+ print x1, y1, 'lineto stroke'
+ print x2, y2, 'moveto'
+ print x3, y3, 'lineto stroke grestore'
+ print 'gsave', x1, y1, 'translate 0.75 dup scale circle fill grestore'
+ print 'gsave', x2, y2, 'translate 0.75 dup scale circle fill grestore'
+ print 'gsave', x3, y3, 'translate 0.75 dup scale circle fill grestore'
+
+
+
+def measure_bz_cloth(seg, bz, n = 100):
+ bz_arclen = tocubic.bz_arclength_rk4(bz)
+ arclen_ratio = seg.arclen / bz_arclen
+ dbz = tocubic.bz_deriv(bz)
+
+ def measure_derivs(x, ys):
+ dx, dy = tocubic.bz_eval(dbz, x)
+ ds = hypot(dx, dy)
+ s = ys[0] * arclen_ratio
+ dscore = ds * (tocubic.mod_2pi(atan2(dy, dx) - seg.th(s)) ** 2)
+ #print s, atan2(dy, dx), seg.th(s)
+ return [ds, dscore]
+ dt = 1./n
+ t = 0
+ ys = [0, 0]
+ for i in range(n):
+ dydx = measure_derivs(t, ys)
+ tocubic.rk4(ys, dydx, t, dt, measure_derivs)
+ t += dt
+ return ys[1]
+
+def cubic_bz_to_pcorn(bz, thresh):
+ dx = bz[3][0] - bz[0][0]
+ dy = bz[3][1] - bz[0][1]
+ dx1 = bz[1][0] - bz[0][0]
+ dy1 = bz[1][1] - bz[0][1]
+ dx2 = bz[3][0] - bz[2][0]
+ dy2 = bz[3][1] - bz[2][1]
+ chth = atan2(dy, dx)
+ th0 = tocubic.mod_2pi(chth - atan2(dy1, dx1))
+ th1 = tocubic.mod_2pi(atan2(dy2, dx2) - chth)
+ seg = pcorn.Segment(bz[0], bz[3], th0, th1)
+ err = measure_bz_cloth(seg, bz)
+ if err < thresh:
+ return [seg]
+ else:
+ # de Casteljau
+ x01, y01 = 0.5 * (bz[0][0] + bz[1][0]), 0.5 * (bz[0][1] + bz[1][1])
+ x12, y12 = 0.5 * (bz[1][0] + bz[2][0]), 0.5 * (bz[1][1] + bz[2][1])
+ x23, y23 = 0.5 * (bz[2][0] + bz[3][0]), 0.5 * (bz[2][1] + bz[3][1])
+ xl2, yl2 = 0.5 * (x01 + x12), 0.5 * (y01 + y12)
+ xr1, yr1 = 0.5 * (x12 + x23), 0.5 * (y12 + y23)
+ xm, ym = 0.5 * (xl2 + xr1), 0.5 * (yl2 + yr1)
+ bzl = [bz[0], (x01, y01), (xl2, yl2), (xm, ym)]
+ bzr = [(xm, ym), (xr1, yr1), (x23, y23), bz[3]]
+ segs = cubic_bz_to_pcorn(bzl, 0.5 * thresh)
+ segs.extend(cubic_bz_to_pcorn(bzr, 0.5 * thresh))
+ return segs
+
+def bzs_to_pcorn(bzs, thresh = 1e-9):
+ result = []
+ for sp in bzs:
+ rsp = []
+ for bz in sp:
+ if len(bz) == 2:
+ dx = bz[1][0] - bz[0][0]
+ dy = bz[1][1] - bz[0][1]
+ th = atan2(dy, dx)
+ rsp.append(pcorn.Segment(bz[0], bz[1], 0, 0))
+ else:
+ rsp.extend(cubic_bz_to_pcorn(bz, thresh))
+ result.append(rsp)
+ return result
+
+def plot_segs(segs):
+ for i in range(len(segs)):
+ seg = segs[i]
+ if i == 0:
+ print seg.z0[0], seg.z0[1], 'moveto'
+ print seg.z1[0], seg.z1[1], 'lineto'
+ print 'stroke'
+ for i in range(len(segs)):
+ seg = segs[i]
+ if i == 0:
+ print 'gsave', seg.z0[0], seg.z0[1], 'translate circle fill grestore'
+ print 'gsave', seg.z1[0], seg.z1[1], 'translate circle fill grestore'
+
+import sys
+
+def test_to_pcorn():
+ C1 = 0.55228
+ bz = [(100, 100), (100 + 400 * C1, 100), (500, 500 - 400 * C1), (500, 500)]
+ for i in range(0, 13):
+ thresh = .1 ** i
+ segs = cubic_bz_to_pcorn(bz, thresh)
+ plot_segs(segs)
+ print >> sys.stderr, thresh, len(segs)
+ print '0 20 translate'
+
+if __name__ == '__main__':
+ f = file(sys.argv[1])
+ bzs = read_bz(f)
+ rsps = bzs_to_pcorn(bzs, 1)
+ #print rsps
+ tocubic.plot_prolog()
+ print 'grestore'
+ print '1 -1 scale 0 -720 translate'
+ print '/ss 1.5 def'
+ print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def'
+ tot = 0
+ for segs in rsps:
+ curve = pcorn.Curve(segs)
+ #curve = offset.offset(curve, 10)
+ print '%', curve.arclen
+ print '%', curve.sstarts
+ if 0:
+ print 'gsave 1 0 0 setrgbcolor'
+ cmd = 'moveto'
+ for i in range(100):
+ s = i * .01 * curve.arclen
+ x, y = curve.xy(s)
+ th = curve.th(s)
+ sth = 5 * sin(th)
+ cth = 5 * cos(th)
+ print x, y, cmd
+ cmd = 'lineto'
+ print 'closepath stroke grestore'
+ for i in range(100):
+ s = i * .01 * curve.arclen
+ x, y = curve.xy(s)
+ th = curve.th(s)
+ sth = 5 * sin(th)
+ cth = 5 * cos(th)
+ if 0:
+ print x - cth, y - sth, 'moveto'
+ print x + cth, y + sth, 'lineto stroke'
+ if 1:
+ for s in curve.find_breaks():
+ print 'gsave 0 1 0 setrgbcolor'
+ x, y = curve.xy(s)
+ print x, y, 'translate 2 dup scale circle fill'
+ print 'grestore'
+ #plot_segs(segs)
+
+ print 'gsave 0 0 0 setrgbcolor'
+ optim = 3
+ thresh = 1e-2
+ new_bzs = tocubic.pcorn_curve_to_bzs(curve, optim, thresh)
+ tot += len(new_bzs)
+ plot_bzs([new_bzs], (0, 0), 1, True)
+ print 'grestore'
+ print 'grestore'
+ print '/Helvetica 12 selectfont'
+ print '36 720 moveto (thresh=%g optim=%d) show' % (thresh, optim)
+ print '( tot segs=%d) show' % tot
+ print 'showpage'
+
+ #plot_bzs(bzs, (100, 100), 1)
diff --git a/third_party/spiro/curves/mecsolve.py b/third_party/spiro/curves/mecsolve.py
new file mode 100644
index 0000000..3840cea
--- /dev/null
+++ b/third_party/spiro/curves/mecsolve.py
@@ -0,0 +1,859 @@
+from math import *
+import array
+import sys
+import random
+
+import numarray as N
+import numarray.linear_algebra as la
+
+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'
+
+# One step of 4th-order Runge-Kutta numerical integration - update y in place
+def rk4(y, dydx, x, h, derivs):
+ hh = h * .5
+ h6 = h * (1./6)
+ xh = x + hh
+ yt = []
+ for i in range(len(y)):
+ yt.append(y[i] + hh * dydx[i])
+ dyt = derivs(xh, yt)
+ for i in range(len(y)):
+ yt[i] = y[i] + hh * dyt[i]
+ dym = derivs(xh, yt)
+ for i in range(len(y)):
+ yt[i] = y[i] + h * dym[i]
+ dym[i] += dyt[i]
+ dyt = derivs(x + h, yt)
+ for i in range(len(y)):
+ y[i] += h6 * (dydx[i] + dyt[i] + 2 * dym[i])
+
+def run_elastica_half(sp, k0, lam1, lam2, n):
+ def mec_derivs(x, ys):
+ th, k = ys[2], ys[3]
+ dx, dy = cos(th), sin(th)
+ return [dx, dy, k, lam1 * dx + lam2 * dy, k * k]
+ ys = [0, 0, 0, k0, 0]
+ xyk = [(ys[0], ys[1], ys[3])]
+ n = max(1, int(sp * n))
+ h = float(sp) / n
+ s = 0
+ for i in range(n):
+ dydx = mec_derivs(s, ys)
+ rk4(ys, dydx, s, h, mec_derivs)
+ xyk.append((ys[0], ys[1], ys[3]))
+ return xyk, ys[2], ys[4]
+
+def run_elastica(sm, sp, k0, lam1, lam2, n = 100):
+ xykm, thm, costm = run_elastica_half(-sm, k0, -lam1, lam2, n)
+ xykp, thp, costp = run_elastica_half(sp, k0, lam1, lam2, n)
+ xyk = []
+ for i in range(1, len(xykm)):
+ x, y, k = xykm[i]
+ xyk.append((-x, y, k))
+ xyk.reverse()
+ xyk.extend(xykp)
+ x = xyk[-1][0] - xyk[0][0]
+ y = xyk[-1][1] - xyk[0][1]
+ th = thm + thp
+ sth, cth = sin(thm), cos(thm)
+ x, y = x * cth - y * sth, x * sth + y * cth
+ result = []
+ maxk = 0
+ for xt, yt, kt in xyk:
+ maxk = max(maxk, kt)
+ result.append((xt, yt, kt))
+ cost = costm + costp
+ return result, cost, x, y, th
+
+def run_mec_cos_pos(k, lam1, lam2, n = 1000):
+ cost = 0
+ th = 0
+ x = 0
+ y = 0
+ dt = 1.0 / n
+ result = [(0, 0)]
+ for i in range(n):
+ k1 = lam1 * cos(th) + lam2 * sin(th)
+
+ cost += dt * k * k
+ x += dt * cos(th)
+ y += dt * sin(th)
+ th += dt * k
+
+ k += dt * k1
+ result.append((x, y))
+ return result, cost, x, y, th
+
+def run_mec_cos(k, lam1, lam2, n = 1000):
+ resp, costp, xp, yp, thp = run_mec_cos_pos(k*.5, lam1*.25, lam2*.25, n)
+ resm, costm, xm, ym, thm = run_mec_cos_pos(k*.5, lam1*-.25, lam2*.25, n)
+ cost = (costp + costm) * .5
+ x, y = xp + xm, yp - ym
+ th = thp + thm
+ sth, cth = .5 * sin(thm), .5 * cos(thm)
+ x, y = x * cth - y * sth, x * sth + y * cth
+ result = []
+ for i in range(1, n):
+ xt, yt = resm[n - i - 1]
+ result.append((-xt * cth - yt * sth, -xt * sth + yt * cth))
+ for i in range(n):
+ xt, yt = resp[i]
+ result.append((xt * cth - yt * sth, xt * sth + yt * cth))
+ return result, cost, x, y, th
+
+def cross_prod(a, b):
+ return [a[1] * b[2] - a[2] * b[1],
+ a[2] * b[0] - a[0] * b[2],
+ a[0] * b[1] - a[1] * b[0]]
+
+def solve_mec(constraint_fnl):
+ delta = 1e-3
+ params = [pi, 0, 0]
+ for i in range(20):
+ k, lam1, lam2 = params
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2)
+ #print i * .05, 'setgray'
+ #plot(xys)
+ c1c, c2c, costc = constraint_fnl(cost, x, y, th)
+ print '% constraint_fnl =', c1c, c2c, 'cost =', costc
+
+ dc1s = []
+ dc2s = []
+ for j in range(len(params)):
+ params1 = N.array(params)
+ params1[j] += delta
+ k, lam1, lam2 = params1
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2)
+ c1p, c2p, costp = constraint_fnl(cost, x, y, th)
+ params1 = N.array(params)
+ params1[j] -= delta
+ k, lam1, lam2 = params1
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2)
+ c1m, c2m, costm = constraint_fnl(cost, x, y, th)
+ dc1s.append((c1p - c1m) / (2 * delta))
+ dc2s.append((c2p - c2m) / (2 * delta))
+ xp = cross_prod(dc1s, dc2s)
+ xp = N.divide(xp, sqrt(N.dot(xp, xp))) # Normalize to unit length
+
+ print '% dc1s =', dc1s
+ print '% dc2s =', dc2s
+ print '% xp =', xp
+
+ # Compute second derivative wrt orthogonal vec
+ params1 = N.array(params)
+ for j in range(len(params)):
+ params1[j] += delta * xp[j]
+ k, lam1, lam2 = params1
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2)
+ c1p, c2p, costp = constraint_fnl(cost, x, y, th)
+ print '% constraint_fnl+ =', c1p, c2p, 'cost =', costp
+ params1 = N.array(params)
+ for j in range(len(params)):
+ params1[j] -= delta * xp[j]
+ k, lam1, lam2 = params1
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2)
+ c1m, c2m, costm = constraint_fnl(cost, x, y, th)
+ print '% constraint_fnl- =', c1m, c2m, 'cost =', costm
+ d2cost = (costp + costm - 2 * costc) / (delta * delta)
+ dcost = (costp - costm) / (2 * delta)
+
+ print '% dcost =', dcost, 'd2cost =', d2cost
+ if d2cost < 0: d2cost = .1
+ # Make Jacobian matrix to invert
+ jm = N.array([dc1s, dc2s, [x * d2cost for x in xp]])
+ #print jm
+ ji = la.inverse(jm)
+ #print ji
+
+ dp = N.dot(ji, [c1c, c2c, dcost])
+ print '% dp =', dp
+ print '% [right]=', [c1c, c2c, dcost]
+ params -= dp * .1
+ print '%', params
+ sys.stdout.flush()
+ return params
+
+def solve_mec_3constr(constraint_fnl, n = 30, initparams = None):
+ delta = 1e-3
+ if initparams:
+ params = N.array(initparams)
+ else:
+ params = [3.14, 0, 0]
+ for i in range(n):
+ k, lam1, lam2 = params
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2)
+ c1c, c2c, c3c = constraint_fnl(cost, x, y, th)
+ print '% constraint_fnl =', c1c, c2c, c3c
+
+ dc1s = []
+ dc2s = []
+ dc3s = []
+ for j in range(len(params)):
+ params1 = N.array(params)
+ params1[j] += delta
+ k, lam1, lam2 = params1
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2)
+ c1p, c2p, c3p = constraint_fnl(cost, x, y, th)
+ params1 = N.array(params)
+ params1[j] -= delta
+ k, lam1, lam2 = params1
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2)
+ c1m, c2m, c3m = constraint_fnl(cost, x, y, th)
+ dc1s.append((c1p - c1m) / (2 * delta))
+ dc2s.append((c2p - c2m) / (2 * delta))
+ dc3s.append((c3p - c3m) / (2 * delta))
+
+ # Make Jacobian matrix to invert
+ jm = N.array([dc1s, dc2s, dc3s])
+ #print jm
+ ji = la.inverse(jm)
+
+ dp = N.dot(ji, [c1c, c2c, c3c])
+ if i < n/2: scale = .25
+ else: scale = 1
+ params -= scale * dp
+ print '%', params
+ return params
+
+def mk_ths_fnl(th0, th1):
+ def fnl(cost, x, y, th):
+ actual_th0 = atan2(y, x)
+ actual_th1 = th - actual_th0
+ print '% x =', x, 'y =', y, 'hypot =', hypot(x, y)
+ return th0 - actual_th0, th1 - actual_th1, cost
+ return fnl
+
+def mk_y_fnl(th0, th1, ytarg):
+ def fnl(cost, x, y, th):
+ actual_th0 = atan2(y, x)
+ actual_th1 = th - actual_th0
+ return th0 - actual_th0, th1 - actual_th1, ytarg - hypot(x, y)
+ return fnl
+
+def solve_mec_nested_inner(th0, th1, y, fnl):
+ innerfnl = mk_y_fnl(th0, th1, y)
+ params = [th0 + th1, 1e-6, 1e-6]
+ params = solve_mec_3constr(innerfnl, 10, params)
+ k, lam1, lam2 = params
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2, 100)
+ c1, c2, c3 = fnl(cost, x, y, th)
+ return c3, params
+
+# Solve (th0, th1) mec to minimize fnl; use solve_mec_3constr as inner loop
+# Use golden section search
+def solve_mec_nested(th0, th1, fnl):
+ R = .61803399
+ C = 1 - R
+ ax, cx = .6, .85
+ bx = .5 * (ax + cx)
+
+ x0, x3 = ax, cx
+ if abs(cx - bx) > abs(bx - ax):
+ x1, x2 = bx, bx + C * (cx - bx)
+ else:
+ x1, x2 = bx - C * (bx - ax), bx
+ f1, p = solve_mec_nested_inner(th0, th1, x1, fnl)
+ f2, p = solve_mec_nested_inner(th0, th1, x2, fnl)
+ for i in range(10):
+ print '%', x0, x1, x2, x3, ':', f1, f2
+ if f2 < f1:
+ x0, x1, x2 = x1, x2, R * x2 + C * x3
+ f1 = f2
+ f2, p = solve_mec_nested_inner(th0, th1, x2, fnl)
+ else:
+ x1, x2, x3 = R * x1 + C * x0, x1, x2
+ f2 = f1
+ f1, p = solve_mec_nested_inner(th0, th1, x1, fnl)
+ if f1 < f2:
+ x = x1
+ else:
+ x = x2
+ cc, p = solve_mec_nested_inner(th0, th1, x, fnl)
+ return p
+
+def plot(xys):
+ cmd = 'moveto'
+ for xy in xys:
+ print 306 + 200 * xy[0], 396 - 200 * xy[1], cmd
+ cmd = 'lineto'
+ print 'stroke'
+
+def mec_test():
+ th0, th1 = 0, pi / 4
+ fnl = mk_ths_fnl(th0, th1)
+ params = solve_mec_nested(th0, th1, fnl)
+ k, lam1, lam2 = params
+ xys, cost, x, y, th = run_mec_cos(k, lam1, lam2, 1000)
+ plot(xys)
+ print '% run_mec_cos:', cost, x, y, th
+ print '1 0 0 setrgbcolor'
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2)
+ print '%', xys
+ plot(xys)
+ print '% run_elastica:', cost, x, y, th
+ print 'showpage'
+ print '%', params
+
+def lenfig():
+ print '306 720 translate'
+ th0, th1 = pi / 2, pi / 2
+ for i in range(1, 10):
+ y = .1 * i + .003
+ fnl = mk_y_fnl(th0, th1, y)
+ params = solve_mec_3constr(fnl)
+ k, lam1, lam2 = params
+ print 'gsave 0.5 dup scale -306 -396 translate'
+ xys, cost, x, y, th = run_elastica(-2, 2, k, lam1, lam2, 100)
+ print 'gsave [2] 0 setdash'
+ plot(xys)
+ print 'grestore'
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2, 100)
+ plot(xys)
+ print 'grestore'
+ print '% y =', y, 'params =', params
+ if lam2 < 0:
+ mymaxk = k
+ else:
+ mymaxk = sqrt(k * k + 4 * lam2)
+ lam = abs(lam2) / (mymaxk * mymaxk)
+ print '-200 0 moveto /Symbol 12 selectfont (l) show'
+ print '/Times-Roman 12 selectfont ( = %.4g) show' % lam
+ print '0 -70 translate'
+ print 'showpage'
+
+def lenplot(figno, th0, th1):
+ result = []
+ if figno in (1, 2):
+ imax = 181
+ elif figno == 3:
+ imax = 191
+ for i in range(1, imax):
+ yy = .005 * i
+ if figno in (1, 2) and i == 127:
+ yy = 2 / pi
+ if figno == 3 and i == 165:
+ yy = .8270
+ fnl = mk_y_fnl(th0, th1, yy)
+ params = solve_mec_3constr(fnl)
+ k, lam1, lam2 = params
+ xys, cost, x0, y0, th = run_elastica(-.5, .5, k, lam1, lam2, 100)
+ if lam2 < 0:
+ mymaxk = k
+ else:
+ mymaxk = sqrt(k * k + 4 * lam2)
+ lam = abs(lam2) / (mymaxk * mymaxk)
+ result.append((yy, lam, cost))
+ return result
+
+def lengraph(figno):
+ if figno in (1, 2):
+ eps_prologue(15, 47, 585, 543)
+ th0, th1 = pi / 2, pi / 2
+ yscale = 900
+ ytic = .05
+ ymax = 10
+ elif figno == 3:
+ eps_prologue(15, 47, 585, 592)
+ th0, th1 = pi / 3, pi / 3
+ yscale = 500
+ ytic = .1
+ ymax = 10
+ x0 = 42
+ xscale = 7.5 * 72
+ y0 = 72
+ print '/Times-Roman 12 selectfont'
+ print '/ss 1.5 def'
+ print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def'
+ print '.5 setlinewidth'
+ print x0, y0, 'moveto', xscale, '0 rlineto 0', yscale * ytic * ymax, 'rlineto', -xscale, '0 rlineto closepath stroke'
+ for i in range(0, 11):
+ print x0 + .1 * i * xscale, y0, 'moveto 0 6 rlineto stroke'
+ print x0 + .1 * i * xscale, y0 + ytic * ymax * yscale, 'moveto 0 -6 rlineto stroke'
+ print x0 + .1 * i * xscale, y0 - 12, 'moveto'
+ print '(%.1g) dup stringwidth exch -.5 mul exch rmoveto show' % (.1 * i)
+ for i in range(0, 11):
+ print x0, y0 + ytic * i * yscale, 'moveto 6 0 rlineto stroke'
+ print x0 + xscale, y0 + ytic * i * yscale, 'moveto -6 0 rlineto stroke'
+ print x0 - 3, y0 + ytic * i * yscale - 3.5, 'moveto'
+ print '(%.2g) dup stringwidth exch neg exch rmoveto show' % (ytic * i)
+ print x0 + .25 * xscale, y0 - 24, 'moveto (chordlen / arclen) show'
+ print x0 - 14, y0 + ytic * ymax * yscale + 10, 'moveto /Symbol 12 selectfont (l) show'
+ if figno in (1, 2):
+ print x0 + 2 / pi * xscale, y0 - 18, 'moveto'
+ print '(2/p) dup stringwidth exch -.5 mul exch rmoveto show'
+ print 'gsave [3] 0 setdash'
+ print x0, y0 + .25 * yscale, 'moveto', xscale, '0 rlineto stroke'
+ if figno == 3:
+ print x0, y0 + .5 * yscale, 'moveto', xscale, '0 rlineto stroke'
+ xinterest = .81153
+ print x0 + xinterest * xscale, y0, 'moveto 0', yscale * .5, 'rlineto stroke'
+ print 'grestore'
+ print '.75 setlinewidth'
+ if 1:
+ if figno in (1, 2):
+ costscale = .01 * yscale
+ elif figno == 3:
+ costscale = .0187 * yscale
+ lenresult = lenplot(figno, th0, th1)
+ cmd = 'moveto'
+ for x, y, cost in lenresult:
+ print x0 + xscale * x, y0 + yscale * y, cmd
+ cmd = 'lineto'
+ print 'stroke'
+ if figno in (2, 3):
+ cmd = 'moveto'
+ for x, y, cost in lenresult:
+ print x0 + xscale * x, y0 + costscale * cost, cmd
+ cmd = 'lineto'
+ print 'stroke'
+ cmd = 'moveto'
+ for x, y, cost in lenresult:
+ print x0 + xscale * x, y0 + costscale * x * cost, cmd
+ cmd = 'lineto'
+ print 'stroke'
+ print '/Times-Roman 12 selectfont'
+ if figno == 2:
+ xlm, ylm = .75, 7
+ xls, yls = .42, 15
+ elif figno == 3:
+ xlm, ylm = .6, 3
+ xls, yls = .37, 15
+ print x0 + xscale * xlm, y0 + costscale * ylm, 'moveto'
+ print '(MEC cost) show'
+ print x0 + xscale * xls, y0 + costscale * yls, 'moveto'
+ print '(SIMEC cost) show'
+ if figno == 1:
+ minis = [(.05, 5, -5),
+ (.26, -40, 10),
+ (.4569466, -5, -10),
+ (.55, 35, 12),
+
+ (.6026, -60, 45),
+ (.6046, -60, 15),
+ (.6066, -60, -15),
+
+ (.6213, -22, 10),
+ (.6366198, 15, 22),
+ (.66, 20, 10),
+ (.9, 0, -10)]
+ elif figno == 2:
+ minis = [(.4569466, -5, -10),
+ (.6366198, 15, 22)]
+ elif figno == 3:
+ minis = [(.741, 55, -10),
+ (.81153, -30, 20),
+ (.8270, 20, 30)]
+
+ for yy, xo, yo in minis:
+ fnl = mk_y_fnl(th0, th1, yy)
+ params = solve_mec_3constr(fnl)
+ k, lam1, lam2 = params
+ if lam2 < 0:
+ mymaxk = k
+ else:
+ mymaxk = sqrt(k * k + 4 * lam2)
+ lam = abs(lam2) / (mymaxk * mymaxk)
+ x = x0 + xscale * yy
+ y = y0 + yscale * lam
+ print 'gsave %g %g translate circle fill' % (x, y)
+ print '%g %g translate 0.15 dup scale' % (xo, yo)
+ print '-306 -396 translate'
+ print '2 setlinewidth'
+ if yy < .6 or yy > .61:
+ s = 2
+ elif yy == .6046:
+ s = 2.8
+ else:
+ s = 5
+ xys, cost, x, y, th = run_elastica(-s, s, k, lam1, lam2, 100)
+ print 'gsave [10] 0 setdash'
+ plot(xys)
+ print 'grestore 6 setlinewidth'
+ xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2, 100)
+ plot(xys)
+ print 'grestore'
+
+ print 'showpage'
+ eps_trailer()
+
+def draw_axes(x0, y0, xscale, yscale, xmax, ymax, nx, ny):
+ print '.5 setlinewidth'
+ print '/Times-Roman 12 selectfont'
+ print x0, y0, 'moveto', xscale * xmax, '0 rlineto 0', yscale * ymax, 'rlineto', -xscale * xmax, '0 rlineto closepath stroke'
+ xinc = (xmax * xscale * 1.) / nx
+ yinc = (ymax * yscale * 1.) / ny
+ for i in range(0, nx + 1):
+ if i > 0 and i < nx + 1:
+ print x0 + xinc * i, y0, 'moveto 0 6 rlineto stroke'
+ print x0 + xinc * i, y0 + ymax * yscale, 'moveto 0 -6 rlineto stroke'
+ print x0 + xinc * i, y0 - 12, 'moveto'
+ print '(%.2g) dup stringwidth exch -.5 mul exch rmoveto show' % ((i * xmax * 1.) / nx)
+ for i in range(0, ny + 1):
+ if i > 0 and i < ny + 1:
+ print x0, y0 + yinc * i, 'moveto 6 0 rlineto stroke'
+ print x0 + xmax * xscale, y0 + yinc * i, 'moveto -6 0 rlineto stroke'
+ print x0 - 3, y0 + yinc * i - 3.5, 'moveto'
+ print '(%.2g) dup stringwidth exch neg exch rmoveto show' % ((i * ymax * 1.) / ny)
+
+
+def mecchord():
+ x0 = 72
+ y0 = 72
+ thscale = 150
+ chscale = 400
+ print '.5 setlinewidth'
+ print '/ss 1.5 def'
+ print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def'
+ draw_axes(x0, y0, thscale, chscale, 3.2, 1, 16, 10)
+ print x0 + 1 * thscale, y0 - 28, 'moveto (angle) show'
+ print 'gsave', x0 - 32, y0 + .25 * chscale, 'translate'
+ print '90 rotate 0 0 moveto (chordlen / arclen) show'
+ print 'grestore'
+
+ cmd = 'moveto'
+ for i in range(0, 67):
+ s = i * .01
+ xys, cost, x, y, th = run_elastica(-s, s, 4, 0, -8, 100)
+ #plot(xys)
+ if s > 0:
+ ch = hypot(x, y) / (2 * s)
+ else:
+ ch = 1
+ print '%', s, x, y, th, ch
+ print x0 + thscale * th / 2, y0 + ch * chscale, cmd
+ cmd = 'lineto'
+ print 'stroke'
+
+ print 'gsave %g %g translate circle fill' % (x0 + thscale * th / 2, y0 + ch * chscale)
+ print '-30 15 translate 0.25 dup scale'
+ print '-306 -396 translate'
+ print '2 setlinewidth'
+ plot(xys)
+ print 'grestore'
+
+ print 'gsave [2] 0 setdash'
+ cmd = 'moveto'
+ for i in range(0, 151):
+ th = pi * i / 150.
+ if th > 0:
+ ch = sin(th) / th
+ else:
+ ch = 1
+ print x0 + thscale * th, y0 + ch * chscale, cmd
+ cmd = 'lineto'
+ print 'stroke'
+ print 'grestore'
+
+ k0 = 4 * .4536 / (2 / pi)
+ s = pi / (2 * k0)
+ xys, cost, x, y, th = run_elastica(-s, s, k0, 0, 0, 100)
+ th = pi
+ ch = 2 / pi
+ print 'gsave %g %g translate circle fill' % (x0 + thscale * th / 2, y0 + ch * chscale)
+ print '30 15 translate 0.25 dup scale'
+ print '-306 -396 translate'
+ print '2 setlinewidth'
+ plot(xys)
+ print 'grestore'
+
+ print x0 + 1.25 * thscale, y0 + .55 * chscale, 'moveto (MEC) show'
+ print x0 + 2.3 * thscale, y0 + .35 * chscale, 'moveto (SIMEC) show'
+
+ print 'showpage'
+
+def trymec(sm, sp):
+ xys, thm, cost = run_elastica_half(abs(sm), 0, 1, 0, 10)
+ xm, ym, km = xys[-1]
+ if sm < 0:
+ xm = -xm
+ ym = -ym
+ thm = -thm
+ xys, thp, cost = run_elastica_half(abs(sp), 0, 1, 0, 10)
+ xp, yp, kp = xys[-1]
+ if sp < 0:
+ xp = -xp
+ yp = -yp
+ thp = -thp
+ chth = atan2(yp - ym, xp - xm)
+ #print xm, ym, xp, yp, chth, thm, thp
+ actual_th0 = chth - thm
+ actual_th1 = thp - chth
+ return actual_th0, actual_th1
+
+def findmec_old(th0, th1):
+ guess_ds = sqrt(6 * (th1 - th0))
+ guess_savg = 2 * (th1 + th0) / guess_ds
+ sm = .5 * (guess_savg - guess_ds)
+ sp = .5 * (guess_savg + guess_ds)
+ #sm, sp = th0, th1
+ for i in range(1):
+ actual_th0, actual_th1 = trymec(sm, sp)
+ guess_dth = 1./6 * (sp - sm) ** 2
+ guess_avth = .5 * (sp + sm) * (sp - sm)
+ guess_th0 = .5 * (guess_avth - guess_dth)
+ guess_th1 = .5 * (guess_avth + guess_dth)
+ print sm, sp, actual_th0, actual_th1, guess_th0, guess_th1
+
+def mecplots():
+ print '2 dup scale'
+ print '-153 -296 translate'
+ scale = 45
+ print '0.25 setlinewidth'
+ print 306 - scale * pi/2, 396, 'moveto', 306 + scale * pi/2, 396, 'lineto stroke'
+ print 306, 396 - scale * pi/2, 'moveto', 306, 396 + scale * pi/2, 'lineto stroke'
+ print '/ss .5 def'
+ print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def'
+
+ tic = .1
+ maj = 5
+ r0, r1 = 0, 81
+
+ for i in range(r0, r1, maj):
+ sm = i * tic
+ cmd = 'moveto'
+ for j in range(i, r1):
+ sp = j * tic + 1e-3
+ th0, th1 = trymec(sm, sp)
+ print '%', sm, sp, th0, th1
+ print 306 + scale * th0, 396 + scale * th1, cmd
+ cmd = 'lineto'
+ print 'stroke'
+ for j in range(r0, r1, maj):
+ sp = j * tic + 1e-3
+ cmd = 'moveto'
+ for i in range(0, j + 1):
+ sm = i * tic
+ th0, th1 = trymec(sm, sp)
+ print '%', sm, sp, th0, th1
+ print 306 + scale * th0, 396 + scale * th1, cmd
+ cmd = 'lineto'
+ print 'stroke'
+
+ for i in range(r0, r1, maj):
+ sm = i * tic
+ for j in range(i, r1, maj):
+ sp = j * tic + 1e-3
+ th0, th1 = trymec(sm, sp)
+ print 'gsave'
+ print 306 + scale * th0, 596 + scale * th1, 'translate'
+ print 'circle fill'
+ uscale = 3
+ xys, thm, cost = run_elastica_half(abs(sm), 0, 1, 0, 100)
+ x0, y0 = xys[-1][0], xys[-1][1]
+ if sm < 0:
+ x0, y0 = -x0, -y0
+ xys, thm, cost = run_elastica_half(abs(sp), 0, 1, 0, 100)
+ x1, y1 = xys[-1][0], xys[-1][1]
+ if sp < 0:
+ x1, y1 = -x1, -y1
+ cmd = 'moveto'
+ for xy in xys:
+ print xy[0] * uscale, xy[1] * uscale, cmd
+ cmd = 'lineto'
+ print 'stroke'
+ print '1 0 0 setrgbcolor'
+ print x0 * uscale, y0 * uscale, 'moveto'
+ print x1 * uscale, y1 * uscale, 'lineto stroke'
+ print 'grestore'
+
+ print 'showpage'
+
+def findmec(th0, th1):
+ delta = 1e-3
+ if th0 < 0 and th0 - th1 < .1:
+ sm = 5.
+ sp = 6.
+ else:
+ sm = 3.
+ sp = 5.
+ params = [sm, sp]
+ lasterr = 1e6
+ for i in range(25):
+ sm, sp = params
+ ath0, ath1 = trymec(sm, sp)
+ c1c, c2c = th0 - ath0, th1 - ath1
+
+ err = c1c * c1c + c2c * c2c
+ if 0:
+ print '%findmec', sm, sp, ath0, ath1, err
+ sys.stdout.flush()
+
+ if err < 1e-9:
+ return params
+ if err > lasterr:
+ return None
+ lasterr = err
+
+
+ dc1s = []
+ dc2s = []
+ for j in range(len(params)):
+ params1 = N.array(params)
+ params1[j] += delta
+ sm, sp = params1
+ ath0, ath1 = trymec(sm, sp)
+ c1p, c2p = th0 - ath0, th1 - ath1
+
+ params1 = N.array(params)
+ params1[j] -= delta
+ sm, sp = params1
+ ath0, ath1 = trymec(sm, sp)
+ c1m, c2m = th0 - ath0, th1 - ath1
+
+ dc1s.append((c1p - c1m) / (2 * delta))
+ dc2s.append((c2p - c2m) / (2 * delta))
+
+ jm = N.array([dc1s, dc2s])
+ ji = la.inverse(jm)
+ dp = N.dot(ji, [c1c, c2c])
+
+ if i < 4:
+ scale = .5
+ else:
+ scale = 1
+ params -= scale * dp
+ if params[0] < 0: params[0] = 0.
+ return params
+
+def mecrange(figtype):
+ scale = 130
+ eps_prologue(50, 110, 570, 630)
+ print -50, 0, 'translate'
+ print '0.5 setlinewidth'
+ thlmin, thlmax = -pi/2, 2.4
+ thrmin, thrmax = -2.2, pi / 2 + .2
+ print 306 + scale * thlmin, 396, 'moveto', 306 + scale * thlmax, 396, 'lineto stroke'
+ print 306, 396 + scale * thrmin, 'moveto', 306, 396 + scale * thrmax, 'lineto stroke'
+
+ print 'gsave [2] 0 setdash'
+ print 306, 396 + scale * pi / 2, 'moveto'
+ print 306 + scale * thlmax, 396 + scale * pi / 2, 'lineto stroke'
+ print 306 + scale * thlmin, 396 - scale * pi / 2, 'moveto'
+ print 306 + scale * thlmax, 396 - scale * pi / 2, 'lineto stroke'
+ print 306 + scale * pi / 2, 396 + scale * thrmin, 'moveto'
+ print 306 + scale * pi / 2, 396 + scale * thrmax, 'lineto stroke'
+ print 'grestore'
+
+ print 306 + 3, 396 + scale * thrmax - 10, 'moveto'
+ print '/Symbol 12 selectfont (q) show'
+ print 0, -2, 'rmoveto'
+ print '/Times-Italic 9 selectfont (right) show'
+
+ print 306 - 18, 396 + scale * pi / 2 - 4, 'moveto'
+ print '/Symbol 12 selectfont (p/2) show'
+ print 306 + scale * 2.2, 396 - scale * pi / 2 + 2, 'moveto'
+ print '/Symbol 12 selectfont (-p/2) show'
+
+ print 306 + scale * pi/2 + 2, 396 + scale * thrmax - 10, 'moveto'
+ print '/Symbol 12 selectfont (p/2) show'
+
+ print 306 + scale * 2.2, 396 + 6, 'moveto'
+ print '/Symbol 12 selectfont (q) show'
+ print 0, -2, 'rmoveto'
+ print '/Times-Italic 9 selectfont (left) show'
+
+ print '/ss 0.8 def'
+ print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def'
+ cmd = 'moveto'
+ for i in range(0, 201):
+ th = (i * .005 - .75 )* pi
+ rmin = 1.5
+ rmax = 2.5
+ for j in range(20):
+ r = (rmin + rmax) * .5
+ th0 = r * cos(th)
+ th1 = r * sin(th)
+ if findmec(th0, th1) == None:
+ rmax = r
+ else:
+ rmin = r
+ r = (rmin + rmax) * .5
+ th0 = r * cos(th)
+ th1 = r * sin(th)
+ print '%', r, th, th0, th1
+ print 306 + scale * th0, 396 + scale * th1, cmd
+ cmd = 'lineto'
+ sys.stdout.flush()
+ print 'stroke'
+ sys.stdout.flush()
+
+ for i in range(-11, 12):
+ for j in range(-11, i + 1):
+ th0, th1 = i * .196, j * .196
+ print '%', th0, th1
+ params = findmec(th0, th1)
+ if params != None:
+ sm, sp = params
+ print 'gsave'
+ print 306 + scale * th0, 396 + scale * th1, 'translate'
+ uscale = 22
+ k0, lam1, lam2 = justify_mec(sm, sp)
+ xys, cost, x, y, th = run_elastica(-.5, .5, k0, lam1, lam2)
+ cmdm = 'moveto'
+ dx = xys[-1][0] - xys[0][0]
+ dy = xys[-1][1] - xys[0][1]
+ ch = hypot(dx, dy)
+ chth = atan2(dy, dx)
+ if figtype == 'mecrange':
+ print 'circle fill'
+ s = uscale * sin(chth) / ch
+ c = uscale * cos(chth) / ch
+ h = -xys[0][0] * s + xys[0][1] * c
+ for xy in xys:
+ print xy[0] * c + xy[1] * s, h + xy[0] * s - xy[1] * c, cmdm
+ cmdm = 'lineto'
+ elif figtype == 'mecrangek':
+ ds = 1. / (len(xys) - 1)
+ sscale = 13. / ch
+ kscale = 3 * ch
+ print 'gsave .25 setlinewidth'
+ print sscale * -.5, 0, 'moveto', sscale, 0, 'rlineto stroke'
+ print 'grestore'
+ for l in range(len(xys)):
+ print sscale * (ds * l - 0.5), kscale * xys[l][2], cmdm
+ cmdm = 'lineto'
+ print 'stroke'
+ print 'grestore'
+ sys.stdout.flush()
+ print 'showpage'
+ eps_trailer()
+
+# given sm, sp in [0, 1, 0] mec, return params for -0.5..0.5 segment
+def justify_mec(sm, sp):
+ sc = (sm + sp) * 0.5
+ xys, thc, cost = run_elastica_half(sc, 0, 1, 0, 100)
+ print '% sc =', sc, 'thc =', thc
+ k0, lam1, lam2 = xys[-1][2], cos(thc), -sin(thc)
+ ds = sp - sm
+ k0 *= ds
+ lam1 *= ds * ds
+ lam2 *= ds * ds
+ return [k0, lam1, lam2]
+
+import sys
+figname = sys.argv[1]
+if len(figname) > 4 and figname[-4:] == '.pdf': figname = figname[:-4]
+if figname in ('lengraph1', 'lengraph2', 'lengraph3'):
+ lengraph(int(figname[-1]))
+ #mec_test()
+ #lenfig()
+elif figname == 'mecchord':
+ mecchord()
+elif figname in ('mecrange', 'mecrangek'):
+ mecrange(figname)
+elif figname == 'mecplots':
+ mecplots()
+elif figname == 'findmec':
+ findmec(-.1, -.1)
+ findmec(-.2, -.2)
+
diff --git a/third_party/spiro/curves/mvc.py b/third_party/spiro/curves/mvc.py
new file mode 100644
index 0000000..19ff927
--- /dev/null
+++ b/third_party/spiro/curves/mvc.py
@@ -0,0 +1,208 @@
+from math import *
+import array
+import sys
+import random
+
+def run_mvc(k, k1, k2, k3, C, n = 100, do_print = False):
+ cmd = 'moveto'
+ result = array.array('d')
+ cost = 0
+ th = 0
+ x = 0
+ y = 0
+ dt = 1.0 / n
+ for i in range(n):
+ k4 = -k * (k * k2 - .5 * k1 * k1 + C)
+
+ cost += dt * k1 * k1
+ x += dt * cos(th)
+ y += dt * sin(th)
+ th += dt * k
+
+ k += dt * k1
+ k1 += dt * k2
+ k2 += dt * k3
+ k3 += dt * k4
+ result.append(k)
+ if do_print: print 400 + 400 * x, 500 + 400 * y, cmd
+ cmd = 'lineto'
+ return result, cost, x, y, th
+
+def run_mec_cos(k, lam1, lam2, n = 100, do_print = False):
+ cmd = 'moveto'
+ result = array.array('d')
+ cost = 0
+ th = 0
+ x = 0
+ y = 0
+ dt = 1.0 / n
+ for i in range(n):
+ k1 = lam1 * cos(th) + lam2 * sin(th)
+
+ cost += dt * k * k
+ x += dt * cos(th)
+ y += dt * sin(th)
+ th += dt * k
+
+ k += dt * k1
+ result.append(k)
+ if do_print: print 400 + 400 * x, 500 + 400 * y, cmd
+ cmd = 'lineto'
+ return result, cost, x, y, th
+
+def descend(params, fnl):
+ delta = 1
+ for i in range(100):
+ best = fnl(params, i, True)
+ bestparams = params
+ for j in range(2 * len(params)):
+ ix = j / 2
+ sign = 1 - 2 * (ix & 1)
+ newparams = params[:]
+ newparams[ix] += delta * sign
+ new = fnl(newparams, i)
+ if (new < best):
+ bestparams = newparams
+ best = new
+ if (bestparams == params):
+ delta *= .5
+ print '%', params, delta
+ sys.stdout.flush()
+ params = bestparams
+ return bestparams
+
+def descend2(params, fnl):
+ delta = 20
+ for i in range(5):
+ best = fnl(params, i, True)
+ bestparams = params
+ for j in range(100000):
+ newparams = params[:]
+ for ix in range(len(params)):
+ newparams[ix] += delta * (2 * random.random() - 1)
+ new = fnl(newparams, i)
+ if (new < best):
+ bestparams = newparams
+ best = new
+ if (bestparams == params):
+ delta *= .5
+ params = bestparams
+ print '%', params, best, delta
+ sys.stdout.flush()
+ return bestparams
+
+def desc_eval(params, dfdp, fnl, i, x):
+ newparams = params[:]
+ for ix in range(len(params)):
+ newparams[ix] += x * dfdp[ix]
+ return fnl(newparams, i)
+
+def descend3(params, fnl):
+ dp = 1e-6
+ for i in range(1000):
+ base = fnl(params, i, True)
+ dfdp = []
+ for ix in range(len(params)):
+ newparams = params[:]
+ newparams[ix] += dp
+ new = fnl(newparams, i)
+ dfdp.append((new - base) / dp)
+ print '% dfdp = ', dfdp
+ xr = 0.
+ yr = base
+ xm = -1e-3
+ ym = desc_eval(params, dfdp, fnl, i, xm)
+ if ym > yr:
+ while ym > yr:
+ xl, yl = xm, ym
+ xm = .618034 * xl
+ ym = desc_eval(params, dfdp, fnl, i, xm)
+ else:
+ xl = 1.618034 * xm
+ yl = desc_eval(params, dfdp, fnl, i, xl)
+ while ym > yl:
+ xm, ym = xl, yl
+ xl = 1.618034 * xm
+ yl = desc_eval(params, dfdp, fnl, i, xl)
+
+ # We have initial bracket; ym < yl and ym < yr
+
+ x0, x3 = xl, xr
+ if abs(xr - xm) > abs(xm - xl):
+ x1, y1 = xm, ym
+ x2 = xm + .381966 * (xr - xm)
+ y2 = desc_eval(params, dfdp, fnl, i, x2)
+ else:
+ x2, y2 = xm, ym
+ x1 = xm + .381966 * (xl - xm)
+ y1 = desc_eval(params, dfdp, fnl, i, x1)
+ for j in range(30):
+ if y2 < y1:
+ x0, x1, x2 = x1, x2, x2 + .381966 * (x3 - x2)
+ y0, y1 = y1, y2
+ y2 = desc_eval(params, dfdp, fnl, i, x2)
+ else:
+ x1, x2, x3 = x1 + .381966 * (x0 - x1), x1, x2
+ y1 = desc_eval(params, dfdp, fnl, i, x1)
+ if y1 < y2:
+ xbest = x1
+ ybest = y1
+ else:
+ xbest = x2
+ ybest = y2
+ for ix in range(len(params)):
+ params[ix] += xbest * dfdp[ix]
+ print '%', params, xbest, ybest
+ sys.stdout.flush()
+ return params
+
+def mk_mvc_fnl(th0, th1):
+ def fnl(params, i, do_print = False):
+ k, k1, k2, k3, C = params
+ ks, cost, x, y, th = run_mvc(k, k1, k2, k3, C, 100)
+ cost *= hypot(y, x) ** 3
+ actual_th0 = atan2(y, x)
+ actual_th1 = th - actual_th0
+ if do_print: print '%', x, y, actual_th0, actual_th1, cost
+ err = (th0 - actual_th0) ** 2 + (th1 - actual_th1) ** 2
+ multiplier = 1000
+ return cost + err * multiplier
+ return fnl
+
+def mk_mec_fnl(th0, th1):
+ def fnl(params, i, do_print = False):
+ k, lam1, lam2 = params
+ ks, cost, x, y, th = run_mec_cos(k, lam1, lam2)
+ cost *= hypot(y, x)
+ actual_th0 = atan2(y, x)
+ actual_th1 = th - actual_th0
+ if do_print: print '%', x, y, actual_th0, actual_th1, cost
+ err = (th0 - actual_th0) ** 2 + (th1 - actual_th1) ** 2
+ multiplier = 10
+ return cost + err * multiplier
+ return fnl
+
+#ks, cost, x, y, th = run_mvc(0, 10, -10, 10, 200)
+#print '%', cost, x, y
+#print 'stroke showpage'
+
+def mvc_test():
+ fnl = mk_mvc_fnl(-pi, pi/4)
+ params = [0, 0, 0, 0, 0]
+ params = descend3(params, fnl)
+ k, k1, k2, k3, C = params
+ run_mvc(k, k1, k2, k3, C, 100, True)
+ print 'stroke showpage'
+ print '%', params
+
+def mec_test():
+ th0, th1 = pi/2, pi/2
+ fnl = mk_mec_fnl(th0, th1)
+ params = [0, 0, 0]
+ params = descend2(params, fnl)
+ k, lam1, lam2 = params
+ run_mec_cos(k, lam1, lam2, 1000, True)
+ print 'stroke showpage'
+ print '%', params
+
+mvc_test()
diff --git a/third_party/spiro/curves/numintsynth.py b/third_party/spiro/curves/numintsynth.py
new file mode 100644
index 0000000..9a51b66
--- /dev/null
+++ b/third_party/spiro/curves/numintsynth.py
@@ -0,0 +1,176 @@
+# Synthesize a procedure to numerically integrate the 3rd order poly spiral
+
+tex = False
+
+if tex:
+ mulsym = ' '
+else:
+ mulsym = ' * '
+
+class Poly:
+ def __init__(self, p0, coeffs):
+ self.p0 = p0
+ self.coeffs = coeffs
+ def eval(self, x):
+ y = x ** self.p0
+ z = 0
+ for c in coeffs:
+ z += y * c
+ y *= x
+ return z
+
+def add(poly0, poly1, nmax):
+ lp0 = len(poly0.coeffs)
+ lp1 = len(poly1.coeffs)
+ p0 = min(poly0.p0, poly1.p0)
+ n = min(max(poly0.p0 + lp0, poly1.p1 + lp1), nmax) - p0
+ if n <= 0: return Poly(0, [])
+ coeffs = []
+ for i in range(n):
+ c = 0
+ if i >= poly0.p0 - p0 and i < lp0 + poly0.p0 - p0:
+ c += poly0.coeffs[i + p0 - poly0.p0]
+ if i >= poly1.p0 - p0 and i < lp1 + poly1.p0 - p0:
+ c += poly1.coeffs[i + p0 - poly1.p0]
+ coeffs.append(c)
+ return Poly(p0, coeffs)
+
+def pr(str):
+ if tex:
+ print str, '\\\\'
+ else:
+ print '\t' + str + ';'
+
+def prd(str):
+ if tex:
+ print str, '\\\\'
+ else:
+ print '\tdouble ' + str + ';'
+
+def polymul(p0, p1, degree, basename, suppress_odd = False):
+ result = []
+ for i in range(min(degree, len(p0) + len(p1) - 1)):
+ terms = []
+ for j in range(i + 1):
+ if j < len(p0) and i - j < len(p1):
+ t0 = p0[j]
+ t1 = p1[i - j]
+ if t0 != None and t1 != None:
+ terms.append(t0 + mulsym + t1)
+ if terms == []:
+ result.append(None)
+ else:
+ var = basename % i
+ if (j % 2 == 0) or not suppress_odd:
+ prd(var + ' = ' + ' + '.join(terms))
+ result.append(var)
+ return result
+
+def polysquare(p0, degree, basename):
+ result = []
+ for i in range(min(degree, 2 * len(p0) - 1)):
+ terms = []
+ for j in range((i + 1)/ 2):
+ if i - j < len(p0):
+ t0 = p0[j]
+ t1 = p0[i - j]
+ if t0 != None and t1 != None:
+ terms.append(t0 + mulsym + t1)
+ if len(terms) >= 1:
+ if tex and len(terms) == 1:
+ terms = ['2 ' + terms[0]]
+ else:
+ terms = ['2' + mulsym + '(' + ' + '.join(terms) + ')']
+ if (i % 2) == 0:
+ t = p0[i / 2]
+ if t != None:
+ if tex:
+ terms.append(t + '^2')
+ else:
+ terms.append(t + mulsym + t)
+ if terms == []:
+ result.append(None)
+ else:
+ var = basename % i
+ prd(var + ' = ' + ' + '.join(terms))
+ result.append(var)
+ return result
+
+def mkspiro(degree):
+ if tex:
+ us = ['u = 1']
+ vs = ['v =']
+ else:
+ us = ['u = 1']
+ vs = ['v = 0']
+ if tex:
+ tp = [None, 't_{11}', 't_{12}', 't_{13}', 't_{14}']
+ else:
+ tp = [None, 't1_1', 't1_2', 't1_3', 't1_4']
+ if tex:
+ prd(tp[1] + ' = k_0')
+ prd(tp[2] + ' = \\frac{k_1}{2}')
+ prd(tp[3] + ' = \\frac{k_2}{6}')
+ prd(tp[4] + ' = \\frac{k_3}{24}')
+ else:
+ prd(tp[1] + ' = km0')
+ prd(tp[2] + ' = .5 * km1')
+ prd(tp[3] + ' = (1./6) * km2')
+ prd(tp[4] + ' = (1./24) * km3')
+ tlast = tp
+ coef = 1.
+ for i in range(1, degree - 1):
+ tmp = []
+ tcoef = coef
+ #print tlast
+ for j in range(len(tlast)):
+ c = tcoef / (j + 1)
+ if (j % 2) == 0 and tlast[j] != None:
+ if tex:
+ tmp.append('\\frac{%s}{%.0f}' % (tlast[j], 1./c))
+ else:
+ if c < 1e-9:
+ cstr = '%.16e' % c
+ else:
+ cstr = '(1./%d)' % int(.5 + (1./c))
+ tmp.append(cstr + ' * ' + tlast[j])
+ tcoef *= .5
+ if tmp != []:
+ sign = ('+', '-')[(i / 2) % 2]
+ var = ('u', 'v')[i % 2]
+ if tex:
+ if i == 1: pref = ''
+ else: pref = sign + ' '
+ str = pref + (' ' + sign + ' ').join(tmp)
+ else:
+ str = var + ' ' + sign + '= ' + ' + '.join(tmp)
+ if var == 'u': us.append(str)
+ else: vs.append(str)
+ if i < degree - 1:
+ if tex:
+ basename = 't_{%d%%d}' % (i + 1)
+ else:
+ basename = 't%d_%%d' % (i + 1)
+ if i == 1:
+ tnext = polysquare(tp, degree - 1, basename)
+ t2 = tnext
+ elif i == 3:
+ tnext = polysquare(t2l, degree - 1, basename)
+ elif (i % 2) == 0:
+ tnext = polymul(tlast, tp, degree - 1, basename, True)
+ else:
+ tnext = polymul(t2l, t2, degree - 1, basename)
+ t2l = tlast
+ tlast = tnext
+ coef /= (i + 1)
+ if tex:
+ pr(' '.join(us))
+ pr(' '.join(vs))
+ else:
+ for u in us:
+ pr(u)
+ for v in vs:
+ pr(v)
+
+if __name__ == '__main__':
+ mkspiro(12)
diff --git a/third_party/spiro/curves/offset.py b/third_party/spiro/curves/offset.py
new file mode 100644
index 0000000..c528aec
--- /dev/null
+++ b/third_party/spiro/curves/offset.py
@@ -0,0 +1,21 @@
+# offset curve of piecewise cornu curves
+
+from math import *
+
+import pcorn
+from clothoid import mod_2pi
+
+def seg_offset(seg, d):
+ th0 = seg.th(0)
+ th1 = seg.th(seg.arclen)
+ z0 = [seg.z0[0] + d * sin(th0), seg.z0[1] - d * cos(th0)]
+ z1 = [seg.z1[0] + d * sin(th1), seg.z1[1] - d * cos(th1)]
+ chth = atan2(z1[1] - z0[1], z1[0] - z0[0])
+ return [pcorn.Segment(z0, z1, mod_2pi(chth - th0), mod_2pi(th1 - chth))]
+
+
+def offset(curve, d):
+ segs = []
+ for seg in curve.segs:
+ segs.extend(seg_offset(seg, d))
+ return pcorn.Curve(segs)
diff --git a/third_party/spiro/curves/pcorn.py b/third_party/spiro/curves/pcorn.py
new file mode 100644
index 0000000..723a82f
--- /dev/null
+++ b/third_party/spiro/curves/pcorn.py
@@ -0,0 +1,160 @@
+# Utilities for piecewise cornu representation of curves
+
+from math import *
+
+import clothoid
+import cornu
+
+class Segment:
+ def __init__(self, z0, z1, th0, th1):
+ self.z0 = z0
+ self.z1 = z1
+ self.th0 = th0
+ self.th1 = th1
+ self.compute()
+ def __repr__(self):
+ return '[' + `self.z0` + `self.z1` + ' ' + `self.th0` + ' ' + `self.th1` + ']'
+ def compute(self):
+ dx = self.z1[0] - self.z0[0]
+ dy = self.z1[1] - self.z0[1]
+ chord = hypot(dy, dx)
+ chth = atan2(dy, dx)
+ k0, k1 = clothoid.solve_clothoid(self.th0, self.th1)
+ charc = clothoid.compute_chord(k0, k1)
+
+ self.chord = chord
+ self.chth = chth
+ self.k0, self.k1 = k0, k1
+ self.charc = charc
+ self.arclen = chord / charc
+ self.thmid = self.chth - self.th0 + 0.5 * self.k0 - 0.125 * self.k1
+
+ self.setup_xy_fresnel()
+
+ def setup_xy_fresnel(self):
+ k0, k1 = self.k0, self.k1
+ if k1 == 0: k1 = 1e-6 # hack
+ if k1 != 0:
+ 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)
+ chord_th = atan2(y1 - y0, x1 - x0)
+ chord = hypot(y1 - y0, x1 - x0)
+ scale = self.chord / chord
+ if k1 >= 0:
+ th = self.chth - chord_th
+ self.mxx = scale * cos(th)
+ self.myx = scale * sin(th)
+ self.mxy = -self.myx
+ self.myy = self.mxx
+ else:
+ th = self.chth + chord_th
+ self.mxx = scale * cos(th)
+ self.myx = scale * sin(th)
+ self.mxy = self.myx
+ self.myy = -self.mxx
+ # rotate -chord_th, flip top/bottom, rotate self.chth
+ self.x0 = self.z0[0] - (self.mxx * x0 + self.mxy * y0)
+ self.y0 = self.z0[1] - (self.myx * x0 + self.myy * y0)
+
+ def th(self, s):
+ u = s / self.arclen - 0.5
+ return self.thmid + (0.5 * self.k1 * u + self.k0) * u
+
+ def xy(self, s):
+ # using fresnel integrals; polynomial approx might be better
+ u = s / self.arclen - 0.5
+ k0, k1 = self.k0, self.k1
+ if k1 == 0: k1 = 1e-6 # hack
+ if k1 != 0:
+ sqrk1 = sqrt(2 * abs(k1))
+ t = (k0 + u * k1) / sqrk1
+ (y, x) = cornu.eval_cornu(t)
+ return [self.x0 + self.mxx * x + self.mxy * y,
+ self.y0 + self.myx * x + self.myy * y]
+
+ def find_extrema(self):
+ # find solutions of th(s) = 0 mod pi/2
+ # todo: find extra solutions when there's an inflection
+ th0 = self.thmid + 0.125 * self.k1 - 0.5 * self.k0
+ th1 = self.thmid + 0.125 * self.k1 + 0.5 * self.k0
+ twooverpi = 2 / pi
+ n0 = int(floor(th0 * twooverpi))
+ n1 = int(floor(th1 * twooverpi))
+ if th1 > th0: signum = 1
+ else: signum = -1
+ result = []
+ for i in range(n0, n1, signum):
+ th = pi/2 * (i + 0.5 * (signum + 1))
+ a = .5 * self.k1
+ b = self.k0
+ c = self.thmid - th
+ if a == 0:
+ u1 = -c/b
+ u2 = 1000
+ else:
+ sqrtdiscrim = sqrt(b * b - 4 * a * c)
+ u1 = (-b - sqrtdiscrim) / (2 * a)
+ u2 = (-b + sqrtdiscrim) / (2 * a)
+ if u1 >= -0.5 and u1 < 0.5:
+ result.append(self.arclen * (u1 + 0.5))
+ if u2 >= -0.5 and u2 < 0.5:
+ result.append(self.arclen * (u2 + 0.5))
+ return result
+
+class Curve:
+ def __init__(self, segs):
+ self.segs = segs
+ self.compute()
+ def compute(self):
+ arclen = 0
+ sstarts = []
+ for seg in self.segs:
+ sstarts.append(arclen)
+ arclen += seg.arclen
+
+ self.arclen = arclen
+ self.sstarts = sstarts
+ def th(self, s, deltas = False):
+ u = s / self.arclen
+ s = self.arclen * (u - floor(u))
+ if s == 0 and not deltas: s = self.arclen
+ i = 0
+ while i < len(self.segs) - 1:
+ # binary search would make a lot of sense here
+ snext = self.sstarts[i + 1]
+ if s < snext or (not deltas and s == snext):
+ break
+ i += 1
+ return self.segs[i].th(s - self.sstarts[i])
+ def xy(self, s):
+ u = s / self.arclen
+ s = self.arclen * (u - floor(u))
+ i = 0
+ while i < len(self.segs) - 1:
+ # binary search would make a lot of sense here
+ if s <= self.sstarts[i + 1]:
+ break
+ i += 1
+ return self.segs[i].xy(s - self.sstarts[i])
+ def find_extrema(self):
+ result = []
+ for i in range(len(self.segs)):
+ seg = self.segs[i]
+ for s in seg.find_extrema():
+ result.append(s + self.sstarts[i])
+ return result
+ def find_breaks(self):
+ result = []
+ for i in range(len(self.segs)):
+ pseg = self.segs[(i + len(self.segs) - 1) % len(self.segs)]
+ seg = self.segs[i]
+ th = clothoid.mod_2pi(pseg.chth + pseg.th1 - (seg.chth - seg.th0))
+ print '% pseg', pseg.chth + pseg.th1, 'seg', seg.chth - seg.th0
+ pisline = pseg.k0 == 0 and pseg.k1 == 0
+ sisline = seg.k0 == 0 and seg.k1 == 0
+ if fabs(th) > 1e-3 or (pisline and not sisline) or (sisline and not pisline):
+ result.append(self.sstarts[i])
+ return result
diff --git a/third_party/spiro/curves/plot_solve_clothoid.py b/third_party/spiro/curves/plot_solve_clothoid.py
new file mode 100644
index 0000000..c611069
--- /dev/null
+++ b/third_party/spiro/curves/plot_solve_clothoid.py
@@ -0,0 +1,164 @@
+import clothoid
+from math import *
+
+print '%!PS-Adobe'
+
+def integ_spiro(k0, k1, k2, k3, n = 4):
+ th1 = k0
+ th2 = .5 * k1
+ th3 = (1./6) * k2
+ th4 = (1./24) * k3
+ ds = 1. / n
+ ds2 = ds * ds
+ ds3 = ds2 * ds
+ s = .5 * ds - .5
+
+ k0 *= ds
+ k1 *= ds
+ k2 *= ds
+ k3 *= ds
+
+ x = 0
+ y = 0
+
+ for i in range(n):
+ if n == 1:
+ km0 = k0
+ km1 = k1 * ds
+ km2 = k2 * ds2
+ else:
+ km0 = (((1./6) * k3 * s + .5 * k2) * s + k1) * s + k0
+ km1 = ((.5 * k3 * s + k2) * s + k1) * ds
+ km2 = (k3 * s + k2) * ds2
+ km3 = k3 * ds3
+
+ t1_1 = km0
+ t1_2 = .5 * km1
+ t1_3 = (1./6) * km2
+ t1_4 = (1./24) * km3
+ t2_2 = t1_1 * t1_1
+ t2_3 = 2 * (t1_1 * t1_2)
+ t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2
+ t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3)
+ t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3
+ t2_7 = 2 * (t1_3 * t1_4)
+ t2_8 = t1_4 * t1_4
+ t3_4 = t2_2 * t1_2 + t2_3 * t1_1
+ t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1
+ t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1
+ t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2
+ t4_4 = t2_2 * t2_2
+ t4_5 = 2 * (t2_2 * t2_3)
+ t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3
+ t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4)
+ t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4
+ t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5)
+ t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5
+ t5_6 = t4_4 * t1_2 + t4_5 * t1_1
+ t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1
+ t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1
+ t6_6 = t4_4 * t2_2
+ t6_7 = t4_4 * t2_3 + t4_5 * t2_2
+ t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2
+ t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2
+ t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2
+ t7_8 = t6_6 * t1_2 + t6_7 * t1_1
+ t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1
+ t8_8 = t6_6 * t2_2
+ t8_9 = t6_6 * t2_3 + t6_7 * t2_2
+ t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2
+ t9_10 = t8_8 * t1_2 + t8_9 * t1_1
+ t10_10 = t8_8 * t2_2
+ u = 1
+ u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8
+ u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8 + (1./270336) * t4_10
+ u -= (1./322560) * t6_6 + (1./1658880) * t6_8 + (1./8110080) * t6_10
+ u += (1./92897280) * t8_8 + (1./454164480) * t8_10
+ u -= 2.4464949595157930e-11 * t10_10
+ v = (1./12) * t1_2 + (1./80) * t1_4
+ v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8 + (1./67584) * t3_10
+ v += (1./53760) * t5_6 + (1./276480) * t5_8 + (1./1351680) * t5_10
+ v -= (1./11612160) * t7_8 + (1./56770560) * t7_10
+ v += 2.4464949595157932e-10 * t9_10
+ if n == 1:
+ x = u
+ y = v
+ else:
+ th = (((th4 * s + th3) * s + th2) * s + th1) * s
+ cth = cos(th)
+ sth = sin(th)
+
+ x += cth * u - sth * v
+ y += cth * v + sth * u
+ s += ds
+ return [x * ds, y * ds]
+
+count_iter = 0
+
+# 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):
+ global count_iter
+
+ k1_old = 0
+ e_old = th1 - th0
+ k0 = th0 + th1
+ k1 = 6 * (1 - ((.5 / pi) * k0) ** 3) * e_old
+
+ # secant method
+ for i in range(10):
+ x, y = integ_spiro(k0, k1, 0, 0)
+ e = (th1 - th0) + 2 * atan2(y, x) - .25 * k1
+ count_iter += 1
+ if verbose:
+ print k0, k1, e
+ if abs(e) < 1e-9: break
+ k1_old, e_old, k1 = k1, e, k1 + (k1_old - k1) * e / (e - e_old)
+
+ return k0, k1
+
+def plot_by_thp():
+ count = 0
+ for i in range(11):
+ thp = i * .1
+ print .5 + .05 * i, .5, .5, 'setrgbcolor'
+ print '.75 setlinewidth'
+ cmd = 'moveto'
+ for j in range(-40, 41):
+ thm = j * .02
+ k0, k1 = solve_clothoid(thp - thm, thp + thm, True)
+ count += 1
+ k1 = min(40, max(-40, k1))
+ print 306 + 75 * thm, 396 - 10 * k1, cmd
+ cmd = 'lineto'
+ print 'stroke'
+ print '% count_iter = ', count_iter, 'for', count
+
+def plot_by_thm():
+ print '.75 setlinewidth'
+ print 36, 396 - 350, 'moveto'
+ print 0, 700, 'rlineto stroke'
+ for i in range(-10, 10):
+ if i == 0: wid = 636
+ else: wid = 5
+ print 36, 396 - 10 * i, 'moveto', wid, '0 rlineto stroke'
+ cmd = 'moveto'
+ thm = -.1
+ for i in range(41):
+ thp = i * .1
+ k0, k1 = solve_clothoid(thp - thm, thp + thm)
+ print 36 + 150 * thp, 396 - 100 * k1, cmd
+ cmd = 'lineto'
+ print 'stroke'
+ print '0 0 1 setrgbcolor'
+ cmd = 'moveto'
+ for i in range(41):
+ thp = i * .1
+ k1 = 12 * thm * cos(.5 * thp)
+ k1 = 12 * thm * (1 - (thp / pi) ** 3)
+ print 36 + 150 * thp, 396 - 100 * k1, cmd
+ cmd = 'lineto'
+ print 'stroke'
+
+plot_by_thp()
diff --git a/third_party/spiro/curves/poly3.py b/third_party/spiro/curves/poly3.py
new file mode 100644
index 0000000..daf6f87
--- /dev/null
+++ b/third_party/spiro/curves/poly3.py
@@ -0,0 +1,148 @@
+# Numerical techniques for solving 3rd order polynomial spline systems
+
+# The standard representation is the vector of derivatives at s=0,
+# with -.5 <= s <= 5.
+#
+# Thus, \kappa(s) = k0 + k1 s + 1/2 k2 s^2 + 1/6 k3 s^3
+
+from math import *
+
+def eval_cubic(a, b, c, d, x):
+ return ((d * x + c) * x + b) * x + a
+
+# integrate over s = [0, 1]
+def int_3spiro_poly(ks, n):
+ x, y = 0, 0
+ th = 0
+ ds = 1.0 / n
+ th1, th2, th3, th4 = ks[0], .5 * ks[1], (1./6) * ks[2], (1./24) * ks[3]
+ k0, k1, k2, k3 = ks[0] * ds, ks[1] * ds, ks[2] * ds, ks[3] * ds
+ s = 0
+ result = [(x, y)]
+ for i in range(n):
+ sm = s + 0.5 * ds
+ th = sm * eval_cubic(th1, th2, th3, th4, sm)
+ cth = cos(th)
+ sth = sin(th)
+
+ km0 = ((1./6 * k3 * sm + .5 * k2) * sm + k1) * sm + k0
+ km1 = ((.5 * k3 * sm + k2) * sm + k1) * ds
+ km2 = (k3 * sm + k2) * ds * ds
+ km3 = k3 * ds * ds * ds
+ #print km0, km1, km2, km3
+ u = 1 - km0 * km0 / 24
+ v = km1 / 24
+
+ u = 1 - km0 * km0 / 24 + (km0 ** 4 - 4 * km0 * km2 - 3 * km1 * km1) / 1920
+ v = km1 / 24 + (km3 - 6 * km0 * km0 * km1) / 1920
+
+ x += cth * u - sth * v
+ y += cth * v + sth * u
+ result.append((ds * x, ds * y))
+
+ s += ds
+
+ return result
+
+def integ_chord(k, n = 64):
+ ks = (k[0] * .5, k[1] * .25, k[2] * .125, k[3] * .0625)
+ xp, yp = int_3spiro_poly(ks, n)[-1]
+ ks = (k[0] * -.5, k[1] * .25, k[2] * -.125, k[3] * .0625)
+ xm, ym = int_3spiro_poly(ks, n)[-1]
+ dx, dy = .5 * (xp + xm), .5 * (yp + ym)
+ return hypot(dx, dy), atan2(dy, dx)
+
+# Return th0, th1, k0, k1 for given params
+def calc_thk(ks):
+ chord, ch_th = integ_chord(ks)
+ th0 = ch_th - (-.5 * ks[0] + .125 * ks[1] - 1./48 * ks[2] + 1./384 * ks[3])
+ th1 = (.5 * ks[0] + .125 * ks[1] + 1./48 * ks[2] + 1./384 * ks[3]) - ch_th
+ k0 = chord * (ks[0] - .5 * ks[1] + .125 * ks[2] - 1./48 * ks[3])
+ k1 = chord * (ks[0] + .5 * ks[1] + .125 * ks[2] + 1./48 * ks[3])
+ #print '%', (-.5 * ks[0] + .125 * ks[1] - 1./48 * ks[2] + 1./384 * ks[3]), (.5 * ks[0] + .125 * ks[1] + 1./48 * ks[2] + 1./384 * ks[3]), ch_th
+ return th0, th1, k0, k1
+
+def calc_k1k2(ks):
+ chord, ch_th = integ_chord(ks)
+ k1l = chord * chord * (ks[1] - .5 * ks[2] + .125 * ks[3])
+ k1r = chord * chord * (ks[1] + .5 * ks[2] + .125 * ks[3])
+ k2l = chord * chord * chord * (ks[2] - .5 * ks[3])
+ k2r = chord * chord * chord * (ks[2] + .5 * ks[3])
+ return k1l, k1r, k2l, k2r
+
+def plot(ks):
+ ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625)
+ pside = int_3spiro_poly(ksp, 64)
+ ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625)
+ mside = int_3spiro_poly(ksm, 64)
+ mside.reverse()
+ for i in range(len(mside)):
+ mside[i] = (-mside[i][0], -mside[i][1])
+ pts = mside + pside[1:]
+ cmd = "moveto"
+ for j in range(len(pts)):
+ x, y = pts[j]
+ print 306 + 300 * x, 400 + 300 * y, cmd
+ cmd = "lineto"
+ print "stroke"
+ x, y = pts[0]
+ print 306 + 300 * x, 400 + 300 * y, "moveto"
+ x, y = pts[-1]
+ print 306 + 300 * x, 400 + 300 * y, "lineto .5 setlinewidth stroke"
+ print "showpage"
+
+def solve_3spiro(th0, th1, k0, k1):
+ ks = [0, 0, 0, 0]
+ for i in range(5):
+ th0_a, th1_a, k0_a, k1_a = calc_thk(ks)
+ dth0 = th0 - th0_a
+ dth1 = th1 - th1_a
+ dk0 = k0 - k0_a
+ dk1 = k1 - k1_a
+ ks[0] += (dth0 + dth1) * 1.5 + (dk0 + dk1) * -.25
+ ks[1] += (dth1 - dth0) * 15 + (dk0 - dk1) * 1.5
+ ks[2] += (dth0 + dth1) * -12 + (dk0 + dk1) * 6
+ ks[3] += (dth0 - dth1) * 360 + (dk1 - dk0) * 60
+ #print '% ks =', ks
+ return ks
+
+def iter_spline(pts, ths, ks):
+ pass
+
+def solve_vee():
+ kss = []
+ for i in range(10):
+ kss.append([0, 0, 0, 0])
+ thl = [0] * len(kss)
+ thr = [0] * len(kss)
+ k0l = [0] * len(kss)
+ k0r = [0] * len(kss)
+ k1l = [0] * len(kss)
+ k1r = [0] * len(kss)
+ k2l = [0] * len(kss)
+ k2r = [0] * len(kss)
+ for i in range(10):
+ for j in range(len(kss)):
+ thl[j], thr[j], k0l[j], k0r[j] = calc_thk(kss[j])
+ k0l[j], k1r[j], k2l[j], k2r[j] = calc_k1k2(kss[j])
+ for j in range(len(kss) - 1):
+ dth = thl[j + 1] + thr[j]
+ if j == 5: dth += .1
+ dk0 = k0l[j + 1] - k0r[j]
+ dk1 = k1l[j + 1] - k1r[j]
+ dk2 = k2l[j + 1] - k2r[j]
+
+
+if __name__ == '__main__':
+ k0 = pi * 3
+ ks = [0, k0, -2 * k0, 0]
+ ks = [0, 0, 0, 0.01]
+ #plot(ks)
+ thk = calc_thk(ks)
+ print '%', thk
+
+ ks = solve_3spiro(0, 0, 0, 0.001)
+ print '% thk =', calc_thk(ks)
+ #plot(ks)
+ print '%', ks
+ print calc_k1k2(ks)
diff --git a/third_party/spiro/curves/polymat-bad.py b/third_party/spiro/curves/polymat-bad.py
new file mode 100644
index 0000000..493fcd8
--- /dev/null
+++ b/third_party/spiro/curves/polymat-bad.py
@@ -0,0 +1,64 @@
+from Numeric import *
+import LinearAlgebra as la
+import sys
+
+n = 15
+m = zeros(((n + 1) * 4, (n + 1) * 4), Float)
+for i in range(n):
+ m[4 * i + 2][4 * i + 0] = .5
+ m[4 * i + 2][4 * i + 1] = -1./12
+ m[4 * i + 2][4 * i + 2] = 1./48
+ m[4 * i + 2][4 * i + 3] = -1./480
+ m[4 * i + 2][4 * i + 4] = .5
+ m[4 * i + 2][4 * i + 5] = 1./12
+ m[4 * i + 2][4 * i + 6] = 1./48
+ m[4 * i + 2][4 * i + 7] = 1./480
+
+ m[4 * i + 3][4 * i + 0] = 1
+ m[4 * i + 3][4 * i + 1] = .5
+ m[4 * i + 3][4 * i + 2] = .125
+ m[4 * i + 3][4 * i + 3] = 1./48
+ m[4 * i + 3][4 * i + 4] = -1
+ m[4 * i + 3][4 * i + 5] = .5
+ m[4 * i + 3][4 * i + 6] = -.125
+ m[4 * i + 3][4 * i + 7] = 1./48
+
+ m[4 * i + 4][4 * i + 0] = 0
+ m[4 * i + 4][4 * i + 1] = 1
+ m[4 * i + 4][4 * i + 2] = .5
+ m[4 * i + 4][4 * i + 3] = .125
+ m[4 * i + 4][4 * i + 4] = 0
+ m[4 * i + 4][4 * i + 5] = -1
+ m[4 * i + 4][4 * i + 6] = .5
+ m[4 * i + 4][4 * i + 7] = -.125
+
+ m[4 * i + 5][4 * i + 0] = 0
+ m[4 * i + 5][4 * i + 1] = 0
+ m[4 * i + 5][4 * i + 2] = 1
+ m[4 * i + 5][4 * i + 3] = .5
+ m[4 * i + 5][4 * i + 4] = 0
+ m[4 * i + 5][4 * i + 5] = 0
+ m[4 * i + 5][4 * i + 6] = -1
+ m[4 * i + 5][4 * i + 7] = .5
+
+m[n * 4 + 2][2] = 1
+m[n * 4 + 3][3] = 1
+
+m[0][n * 4 + 2] = 1
+m[1][n * 4 + 3] = 1
+
+def printarr(m):
+ for j in range(n * 4 + 4):
+ for i in range(n * 4 + 4):
+ print '%6.1f' % m[j][i],
+ print ''
+
+sys.output_line_width = 160
+#print array2string(m, precision = 3)
+mi = la.inverse(m)
+#printarr(mi)
+print ''
+for j in range(n + 1):
+ for k in range(4):
+ print '%7.2f' % mi[j * 4 + k][(n / 2) * 4 + 2],
+ print ''
diff --git a/third_party/spiro/curves/polymat.py b/third_party/spiro/curves/polymat.py
new file mode 100644
index 0000000..a05e2bf
--- /dev/null
+++ b/third_party/spiro/curves/polymat.py
@@ -0,0 +1,399 @@
+import sys
+from math import *
+
+from Numeric import *
+import LinearAlgebra as la
+
+import poly3
+
+n = 15
+m = zeros(((n + 1) * 4, (n + 1) * 4), Float)
+for i in range(n):
+ m[4 * i + 2][4 * i + 0] = .5
+ m[4 * i + 2][4 * i + 1] = -1./12
+ m[4 * i + 2][4 * i + 2] = 1./48
+ m[4 * i + 2][4 * i + 3] = -1./480
+ m[4 * i + 2][4 * i + 4] = .5
+ m[4 * i + 2][4 * i + 5] = 1./12
+ m[4 * i + 2][4 * i + 6] = 1./48
+ m[4 * i + 2][4 * i + 7] = 1./480
+
+ m[4 * i + 3][4 * i + 0] = 1
+ m[4 * i + 3][4 * i + 1] = .5
+ m[4 * i + 3][4 * i + 2] = .125
+ m[4 * i + 3][4 * i + 3] = 1./48
+ m[4 * i + 3][4 * i + 4] = -1
+ m[4 * i + 3][4 * i + 5] = .5
+ m[4 * i + 3][4 * i + 6] = -.125
+ m[4 * i + 3][4 * i + 7] = 1./48
+
+ m[4 * i + 4][4 * i + 0] = 0
+ m[4 * i + 4][4 * i + 1] = 1
+ m[4 * i + 4][4 * i + 2] = .5
+ m[4 * i + 4][4 * i + 3] = .125
+ m[4 * i + 4][4 * i + 4] = 0
+ m[4 * i + 4][4 * i + 5] = -1
+ m[4 * i + 4][4 * i + 6] = .5
+ m[4 * i + 4][4 * i + 7] = -.125
+
+ m[4 * i + 5][4 * i + 0] = 0
+ m[4 * i + 5][4 * i + 1] = 0
+ m[4 * i + 5][4 * i + 2] = 1
+ m[4 * i + 5][4 * i + 3] = .5
+ m[4 * i + 5][4 * i + 4] = 0
+ m[4 * i + 5][4 * i + 5] = 0
+ m[4 * i + 5][4 * i + 6] = -1
+ m[4 * i + 5][4 * i + 7] = .5
+
+m[n * 4 + 2][2] = 1
+m[n * 4 + 3][3] = 1
+
+m[0][n * 4 + 2] = 1
+m[1][n * 4 + 3] = 1
+
+def printarr(m):
+ for j in range(n * 4 + 4):
+ for i in range(n * 4 + 4):
+ print '%6.1f' % m[j][i],
+ print ''
+
+sys.output_line_width = 160
+#print array2string(m, precision = 3)
+mi = la.inverse(m)
+#printarr(mi)
+
+# fit arc to pts (0, 0), (x, y), and (1, 0), return th tangent to
+# arc at (x, y)
+def fit_arc(x, y):
+ th = atan2(y - 2 * x * y, y * y + x - x * x)
+ return th
+
+def mod_2pi(th):
+ u = th / (2 * pi)
+ return 2 * pi * (u - floor(u + 0.5))
+
+def plot_path(path, th, k):
+ if path[0][2] == '{': closed = 0
+ else: closed = 1
+ j = 0
+ cmd = 'moveto'
+ for i in range(len(path) + closed - 1):
+ i1 = (i + 1) % len(path)
+ x0, y0, t0 = path[i]
+ x1, y1, t1 = path[i1]
+ j1 = (j + 1) % len(th)
+ th0 = th[j]
+ k0 = k[j]
+ th1 = th[j1]
+ k1 = k[j1]
+ chord_th = atan2(y1 - y0, x1 - x0)
+ chord_len = hypot(y1 - y0, x1 - x0)
+ ks = poly3.solve_3spiro(mod_2pi(chord_th - th0),
+ mod_2pi(th1 - chord_th),
+ k0 * chord_len, k1 * chord_len)
+ ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625)
+ pside = poly3.int_3spiro_poly(ksp, 64)
+ ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625)
+ mside = poly3.int_3spiro_poly(ksm, 64)
+ mside.reverse()
+ for i in range(len(mside)):
+ mside[i] = (-mside[i][0], -mside[i][1])
+ pts = mside + pside[1:]
+ rot = chord_th - atan2(pts[-1][1] - pts[0][1], pts[-1][0] - pts[0][0])
+ scale = hypot(x1 - x0, y1 - y0) / 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]
+ for x, y in pts:
+ print xt + u * x - v * y, yt + u * y + v * x, cmd
+ cmd = 'lineto'
+ if t1 == 'v':
+ j += 2
+ else:
+ j += 1
+ print 'stroke'
+ if 0:
+ j = 0
+ for i in range(len(path)):
+ x0, y0, t0 = path[i]
+ print 'gsave', x0, y0, 'translate'
+ print ' ', th[j] * 180 / pi, 'rotate'
+ print ' -50 0 moveto 50 0 lineto stroke'
+ print 'grestore'
+ j += 1
+
+def plot_ks(path, th, k):
+ if path[0][2] == '{': closed = 0
+ else: closed = 1
+ j = 0
+ cmd = 'moveto'
+ xo = 36
+ yo = 550
+ xscale = .5
+ yscale = 2000
+ x = xo
+ for i in range(len(path) + closed - 1):
+ i1 = (i + 1) % len(path)
+ x0, y0, t0 = path[i]
+ x1, y1, t1 = path[i1]
+ j1 = (j + 1) % len(th)
+ th0 = th[j]
+ k0 = k[j]
+ th1 = th[j1]
+ k1 = k[j1]
+ chord_th = atan2(y1 - y0, x1 - x0)
+ chord_len = hypot(y1 - y0, x1 - x0)
+ ks = poly3.solve_3spiro(mod_2pi(chord_th - th0),
+ mod_2pi(th1 - chord_th),
+ k0 * chord_len, k1 * chord_len)
+ ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625)
+ pside = poly3.int_3spiro_poly(ksp, 64)
+ ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625)
+ mside = poly3.int_3spiro_poly(ksm, 64)
+ print '%', x0, y0, k0, k1
+ print "% ks = ", ks
+ l = 2 * chord_len / hypot(pside[-1][0] + mside[-1][0], pside[-1][1] + mside[-1][1])
+ for i in range(100):
+ s = .01 * i - 0.5
+ kp = poly3.eval_cubic(ks[0], ks[1], .5 * ks[2], 1./6 * ks[3], s)
+ kp /= l
+ print x + xscale * l * (s + .5), yo + yscale * kp, cmd
+ cmd = 'lineto'
+ x += xscale * l
+ if t1 == 'v':
+ j += 2
+ else:
+ j += 1
+ print 'stroke'
+ print xo, yo, 'moveto', x, yo, 'lineto stroke'
+
+def make_error_vec(path, th, k):
+ if path[0][2] == '{': closed = 0
+ else: closed = 1
+ n = len(path)
+ v = zeros(n * 2, Float)
+ j = 0
+ for i in range(len(path) + closed - 1):
+ i1 = (i + 1) % len(path)
+ x0, y0, t0 = path[i]
+ x1, y1, t1 = path[i1]
+ j1 = (j + 1) % len(th)
+ th0 = th[j]
+ k0 = k[j]
+ th1 = th[j1]
+ k1 = k[j1]
+ chord_th = atan2(y1 - y0, x1 - x0)
+ chord_len = hypot(y1 - y0, x1 - x0)
+ ks = poly3.solve_3spiro(mod_2pi(chord_th - th0),
+ mod_2pi(th1 - chord_th),
+ k0 * chord_len, k1 * chord_len)
+ ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625)
+ pside = poly3.int_3spiro_poly(ksp, 64)
+ ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625)
+ mside = poly3.int_3spiro_poly(ksm, 64)
+ l = chord_len / hypot(pside[-1][0] + mside[-1][0], pside[-1][1] + mside[-1][1])
+ k1l = (ks[1] - .5 * ks[2] + .125 * ks[3]) / (l * l)
+ k1r = (ks[1] + .5 * ks[2] + .125 * ks[3]) / (l * l)
+ k2l = (ks[2] - .5 * ks[3]) / (l * l * l)
+ k2r = (ks[2] + .5 * ks[3]) / (l * l * l)
+
+ if t0 == 'o' or t0 == '[' or t0 == 'v':
+ v[2 * j] -= k1l
+ v[2 * j + 1] -= k2l
+ elif t0 == 'c':
+ v[2 * j + 1] += k2l
+
+ if t1 == 'o' or t1 == ']' or t1 == 'v':
+ v[2 * j1] += k1r
+ v[2 * j1 + 1] += k2r
+ elif t1 == 'c':
+ v[2 * j1] += k2r
+
+ print "% left k'", k1l, "k''", k2l, "right k'", k1r, "k''", k2r
+ if t1 == 'v':
+ j += 2
+ else:
+ j += 1
+ print '% error vector:'
+ for i in range(n):
+ print '% ', v[2 * i], v[2 * i + 1]
+ return v
+
+def add_k1l(m, row, col, col1, l, s):
+ l2 = l * l
+ m[row][2 * col] += s * 36 / l2
+ m[row][2 * col + 1] -= s * 9 / l
+ m[row][2 * col1] += s * 24 / l2
+ m[row][2 * col1 + 1] += s * 3 / l
+
+def add_k1r(m, row, col, col1, l, s):
+ l2 = l * l
+ m[row][2 * col] += s * 24 / l2
+ m[row][2 * col + 1] -= s * 3 / l
+ m[row][2 * col1] += s * 36 / l2
+ m[row][2 * col1 + 1] += s * 9 / l
+
+def add_k2l(m, row, col, col1, l, s):
+ l2 = l * l
+ l3 = l2 * l
+ m[row][2 * col] -= s * 192 / l3
+ m[row][2 * col + 1] += s * 36 / l2
+ m[row][2 * col1] -= s * 168 / l3
+ m[row][2 * col1 + 1] -= s * 24 / l2
+
+def add_k2r(m, row, col, col1, l, s):
+ l2 = l * l
+ l3 = l2 * l
+ m[row][2 * col] += s * 168 / l3
+ m[row][2 * col + 1] -= s * 24 / l2
+ m[row][2 * col1] += s * 192 / l3
+ m[row][2 * col1 + 1] += s * 36 / l2
+
+def make_matrix(path, th, k):
+ if path[0][2] == '{': closed = 0
+ else: closed = 1
+ n = len(path)
+ m = zeros((n * 2, n * 2), Float)
+ j = 0
+ for i in range(len(path) + closed - 1):
+ i1 = (i + 1) % len(path)
+ x0, y0, t0 = path[i]
+ x1, y1, t1 = path[i1]
+ j1 = (j + 1) % len(th)
+ th0 = th[j]
+ k0 = k[j]
+ th1 = th[j1]
+ k1 = k[j1]
+ chord_th = atan2(y1 - y0, x1 - x0)
+ chord_len = hypot(y1 - y0, x1 - x0)
+ ks = poly3.solve_3spiro(mod_2pi(chord_th - th0),
+ mod_2pi(th1 - chord_th),
+ k0 * chord_len, k1 * chord_len)
+ ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625)
+ pside = poly3.int_3spiro_poly(ksp, 64)
+ ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625)
+ mside = poly3.int_3spiro_poly(ksm, 64)
+ l = chord_len / hypot(pside[-1][0] + mside[-1][0], pside[-1][1] + mside[-1][1])
+
+ if t0 == 'o' or t0 == '[' or t0 == 'v':
+ add_k1l(m, 2 * j, j, j1, l, -1)
+ add_k2l(m, 2 * j + 1, j, j1, l, -1)
+ elif t0 == 'c':
+ add_k2l(m, 2 * j + 1, j, j1, l, 1)
+
+ if t1 == 'o' or t1 == ']' or t1 == 'v':
+ add_k1r(m, 2 * j1, j, j1, l, 1)
+ add_k2r(m, 2 * j1 + 1, j, j1, l, 1)
+ elif t1 == 'c':
+ add_k2r(m, 2 * j1, j, j1, l, 1)
+
+ if t1 == 'v':
+ j += 2
+ else:
+ j += 1
+ return m
+
+def solve(path):
+ if path[0][2] == '{': closed = 0
+ else: closed = 1
+ dxs = []
+ dys = []
+ chords = []
+ for i in range(len(path) - 1):
+ dxs.append(path[i + 1][0] - path[i][0])
+ dys.append(path[i + 1][1] - path[i][1])
+ chords.append(hypot(dxs[-1], dys[-1]))
+ nominal_th = []
+ nominal_k = []
+ if not closed:
+ nominal_th.append(atan2(dys[0], dxs[0]))
+ nominal_k.append(0)
+ for i in range(1 - closed, len(path) - 1 + closed):
+ x0, y0, t0 = path[(i + len(path) - 1) % len(path)]
+ x1, y1, t1 = path[i]
+ x2, y2, t2 = path[(i + 1) % len(path)]
+ dx = float(x2 - x0)
+ dy = float(y2 - y0)
+ ir2 = dx * dx + dy * dy
+ x = ((x1 - x0) * dx + (y1 - y0) * dy) / ir2
+ y = ((y1 - y0) * dx - (x1 - x0) * dy) / ir2
+ th = fit_arc(x, y) + atan2(dy, dx)
+ bend_angle = mod_2pi(atan2(y2 - y1, x2 - x1) - atan2(y1 - y0, x1 - x0))
+ k = 2 * bend_angle/(hypot(y2 - y1, x2 - x1) + hypot(y1 - y0, x1 - x0))
+ print '% bend angle', bend_angle, 'k', k
+ if t1 == ']':
+ th = atan2(y1 - y0, x1 - x0)
+ k = 0
+ elif t1 == '[':
+ th = atan2(y2 - y1, x2 - x1)
+ k = 0
+ nominal_th.append(th)
+ nominal_k.append(k)
+ if not closed:
+ nominal_th.append(atan2(dys[-1], dxs[-1]))
+ nominal_k.append(0)
+ print '%', nominal_th
+ print '0 0 1 setrgbcolor .5 setlinewidth'
+ plot_path(path, nominal_th, nominal_k)
+ plot_ks(path, nominal_th, nominal_k)
+ th = nominal_th[:]
+ k = nominal_k[:]
+ n = 8
+ for i in range(n):
+ ev = make_error_vec(path, th, k)
+ m = make_matrix(path, th, k)
+ #print m
+ #print 'inverse:'
+ #print la.inverse(m)
+ v = dot(la.inverse(m), ev)
+ #print v
+ for j in range(len(path)):
+ th[j] += 1. * v[2 * j]
+ k[j] -= 1. * .5 * v[2 * j + 1]
+ 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'
+ plot_path(path, th, k)
+ plot_ks(path, th, k)
+ print '% th:', th
+ print '% k:', k
+
+path = [(100, 100, 'o'), (200, 250, 'o'), (350, 225, 'o'),
+ (450, 350, 'c'), (450, 200, 'o'), (300, 100, 'o')]
+if 0:
+ path = [(100, 480, '['), (100, 300, ']'), (300, 100, 'o'),
+ (500, 300, '['), (500, 480, ']'), (480, 500, '['),
+ (120, 500, ']')]
+
+
+path = [(100, 480, ']'), (100, 120, '['),
+ (120, 100, ']'), (480, 100, '['),
+ (500, 120, ']'), (500, 480, '['),
+ (480, 500, ']'), (120, 500, '[')]
+
+path = [(100, 120, '['), (120, 100, ']'),
+ (140, 100, 'o'), (160, 100, 'o'), (180, 100, 'o'), (200, 100, 'o'),
+ (250, 250, 'o'),
+ (100, 200, 'o'), (100, 180, 'o'), (100, 160, 'o'), (100, 140, 'o')]
+
+path = [(100, 350, 'o'), (225, 350, 'o'), (350, 450, 'o'),
+ (450, 400, 'o'), (315, 205, 'o'), (300, 200, 'o'),
+ (285, 205, 'o')]
+
+if 0:
+ path = []
+ path.append((350, 600, 'c'))
+ path.append((50, 450, 'c'))
+ for i in range(10):
+ path.append((50 + i * 30, 300 - i * 5, 'c'))
+ for i in range(11):
+ path.append((350 + i * 30, 250 + i * 5, 'c'))
+ path.append((650, 450, 'c'))
+
+solve(path)
+print 'showpage'
diff --git a/third_party/spiro/curves/tocubic.py b/third_party/spiro/curves/tocubic.py
new file mode 100644
index 0000000..d70b9a2
--- /dev/null
+++ b/third_party/spiro/curves/tocubic.py
@@ -0,0 +1,461 @@
+# Some code to convert arbitrary curves to high quality cubics.
+
+# Some conventions: points are (x, y) pairs. Cubic Bezier segments are
+# lists of four points.
+
+import sys
+
+from math import *
+
+import pcorn
+
+def pt_wsum(points, wts):
+ x, y = 0, 0
+ for i in range(len(points)):
+ x += points[i][0] * wts[i]
+ y += points[i][1] * wts[i]
+ return x, y
+
+# Very basic spline primitives
+def bz_eval(bz, t):
+ degree = len(bz) - 1
+ mt = 1 - t
+ if degree == 3:
+ return pt_wsum(bz, [mt * mt * mt, 3 * mt * mt * t, 3 * mt * t * t, t * t * t])
+ elif degree == 2:
+ return pt_wsum(bz, [mt * mt, 2 * mt * t, t * t])
+ elif degree == 1:
+ return pt_wsum(bz, [mt, t])
+
+def bz_deriv(bz):
+ degree = len(bz) - 1
+ return [(degree * (bz[i + 1][0] - bz[i][0]), degree * (bz[i + 1][1] - bz[i][1])) for i in range(degree)]
+
+def bz_arclength(bz, n = 10):
+ # We're just going to integrate |z'| over the parameter [0..1].
+ # The integration algorithm here is eqn 4.1.14 from NRC2, and is
+ # chosen for simplicity. Likely adaptive and/or higher-order
+ # algorithms would be better, but this should be good enough.
+ # Convergence should be quartic in n.
+ wtarr = (3./8, 7./6, 23./24)
+ dt = 1./n
+ s = 0
+ dbz = bz_deriv(bz)
+ for i in range(0, n + 1):
+ if i < 3:
+ wt = wtarr[i]
+ elif i > n - 3:
+ wt = wtarr[n - i]
+ else:
+ wt = 1.
+ dx, dy = bz_eval(dbz, i * dt)
+ ds = hypot(dx, dy)
+ s += wt * ds
+ return s * dt
+
+# One step of 4th-order Runge-Kutta numerical integration - update y in place
+def rk4(y, dydx, x, h, derivs):
+ hh = h * .5
+ h6 = h * (1./6)
+ xh = x + hh
+ yt = []
+ for i in range(len(y)):
+ yt.append(y[i] + hh * dydx[i])
+ dyt = derivs(xh, yt)
+ for i in range(len(y)):
+ yt[i] = y[i] + hh * dyt[i]
+ dym = derivs(xh, yt)
+ for i in range(len(y)):
+ yt[i] = y[i] + h * dym[i]
+ dym[i] += dyt[i]
+ dyt = derivs(x + h, yt)
+ for i in range(len(y)):
+ y[i] += h6 * (dydx[i] + dyt[i] + 2 * dym[i])
+
+def bz_arclength_rk4(bz, n = 10):
+ dbz = bz_deriv(bz)
+ def arclength_deriv(x, ys):
+ dx, dy = bz_eval(dbz, x)
+ return [hypot(dx, dy)]
+ dt = 1./n
+ t = 0
+ ys = [0]
+ for i in range(n):
+ dydx = arclength_deriv(t, ys)
+ rk4(ys, dydx, t, dt, arclength_deriv)
+ t += dt
+ return ys[0]
+
+# z0 and z1 are start and end points, resp.
+# th0 and th1 are the initial and final tangents, measured in the
+# direction of the curve.
+# aab is a/(a + b), where a and b are the lengths of the bezier "arms"
+def fit_cubic_arclen(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 * chord
+ darmlen = 1e-6 * armlen
+ for i in range(10):
+ 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]
+ actual_s = bz_arclength_rk4(bz)
+ if (abs(arclen - actual_s) < 1e-12):
+ break
+ a = (armlen + darmlen) * aab
+ b = (armlen + darmlen) - a
+ bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a),
+ (z1[0] + cth1 * b, z1[1] + sth1 * b), z1]
+ actual_s2 = bz_arclength_rk4(bz)
+ ds = (actual_s2 - actual_s) / darmlen
+ #print '% armlen = ', armlen
+ if ds == 0:
+ break
+ armlen += (arclen - actual_s) / ds
+ 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 mod_2pi(th):
+ u = th / (2 * pi)
+ return 2 * pi * (u - floor(u + 0.5))
+
+def measure_bz(bz, arclen, th_fn, n = 1000):
+ dt = 1./n
+ dbz = bz_deriv(bz)
+ s = 0
+ score = 0
+ for i in range(n):
+ dx, dy = bz_eval(dbz, (i + .5) * dt)
+ ds = dt * hypot(dx, dy)
+ s += ds
+ score += ds * (mod_2pi(atan2(dy, dx) - th_fn(s)) ** 2)
+ return score
+
+def measure_bz_rk4(bz, arclen, th_fn, n = 10):
+ dbz = bz_deriv(bz)
+ def measure_derivs(x, ys):
+ dx, dy = bz_eval(dbz, x)
+ ds = hypot(dx, dy)
+ s = ys[0]
+ dscore = ds * (mod_2pi(atan2(dy, dx) - th_fn(s)) ** 2)
+ return [ds, dscore]
+ dt = 1./n
+ t = 0
+ ys = [0, 0]
+ for i in range(n):
+ dydx = measure_derivs(t, ys)
+ rk4(ys, dydx, t, dt, measure_derivs)
+ t += dt
+ return ys[1]
+
+# th_fn() is a function that takes an arclength from the start point, and
+# returns an angle - thus th_fn(0) and th_fn(arclen) are the initial and
+# final tangents.
+# z0, z1, and arclen are as fit_cubic_arclen
+def fit_cubic(z0, z1, arclen, th_fn, fast = 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
+ aabmin = 0
+ aabmax = 1.
+ 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
+ bz = fit_cubic_arclen(z0, z1, arclen, th0, th1, aab)
+ score = 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 plot_prolog():
+ print '%!PS'
+ print '/m { moveto } bind def'
+ print '/l { lineto } bind def'
+ print '/c { curveto } bind def'
+ print '/z { closepath } bind def'
+
+def plot_bz(bz, z0, scale, do_moveto = True):
+ x0, y0 = z0
+ if do_moveto:
+ print bz[0][0] * scale + x0, bz[0][1] * scale + y0, 'm'
+ if len(bz) == 4:
+ x1, y1 = bz[1][0] * scale + x0, bz[1][1] * scale + y0
+ x2, y2 = bz[2][0] * scale + x0, bz[2][1] * scale + y0
+ x3, y3 = bz[3][0] * scale + x0, bz[3][1] * scale + y0
+ print x1, y1, x2, y2, x3, y3, 'c'
+ elif len(bz) == 2:
+ print bz[1][0] * scale + x0, bz[1][1] * scale + y0, 'l'
+
+def test_bz_arclength():
+ bz = [(0, 0), (.5, 0), (1, 0.5), (1, 1)]
+ ans = bz_arclength_rk4(bz, 2048)
+ last = 1
+ lastrk = 1
+ for i in range(3, 11):
+ n = 1 << i
+ err = bz_arclength(bz, n) - ans
+ err_rk = bz_arclength_rk4(bz, n) - ans
+ print n, err, last / err, err_rk, lastrk / err_rk
+ last = err
+ lastrk = err_rk
+
+def test_fit_cubic_arclen():
+ th = pi / 4
+ arclen = th / sin(th)
+ bz = fit_cubic_arclen((0, 0), (1, 0), arclen, th, th, .5)
+ print '%', bz
+ plot_bz(bz, (100, 400), 500)
+ print 'stroke'
+ print 'showpage'
+
+# -- cornu fitting
+
+import cornu
+
+def cornu_to_cubic(t0, t1):
+ 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, 0)
+ return bz, score
+
+def test_draw_cornu():
+ plot_prolog()
+ thresh = 1e-6
+ print '/ss 1.5 def'
+ print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def'
+ s0 = 0
+ imax = 200
+ x0, y0, scale = 36, 100, 500
+ bzs = []
+ for i in range(1, imax):
+ s = sqrt(i * .1)
+ bz, score = cornu_to_cubic(s0, s)
+ if score > (s - s0) * thresh or i == imax - 1:
+ plot_bz(bz, (x0, y0), scale, s0 == 0)
+ bzs.append(bz)
+ s0 = s
+ print 'stroke'
+ for i in range(len(bzs)):
+ bz = bzs[i]
+ bx0, by0 = x0 + bz[0][0] * scale, y0 + bz[0][1] * scale
+ bx1, by1 = x0 + bz[1][0] * scale, y0 + bz[1][1] * scale
+ bx2, by2 = x0 + bz[2][0] * scale, y0 + bz[2][1] * scale
+ bx3, by3 = x0 + bz[3][0] * scale, y0 + bz[3][1] * scale
+ print 'gsave 0 0 1 setrgbcolor .5 setlinewidth'
+ print bx0, by0, 'moveto', bx1, by1, 'lineto stroke'
+ print bx2, by2, 'moveto', bx3, by3, 'lineto stroke'
+ print 'grestore'
+ print 'gsave', bx0, by0, 'translate circle fill grestore'
+ print 'gsave', bx1, by1, 'translate .5 dup scale circle fill grestore'
+ print 'gsave', bx2, by2, 'translate .5 dup scale circle fill grestore'
+ print 'gsave', bx3, by3, 'translate circle fill grestore'
+
+# -- fitting of piecewise cornu curves
+
+def pcorn_segment_to_bzs_optim_inner(curve, s0, s1, thresh, nmax = None):
+ result = []
+ if s0 == s1: return [], 0
+ while s0 < s1:
+ def th_fn_inner(s):
+ if s > s1: s = s1
+ return curve.th(s0 + s, s == 0)
+ z0 = curve.xy(s0)
+ z1 = curve.xy(s1)
+ bz, score = fit_cubic(z0, z1, s1 - s0, th_fn_inner, 0)
+ if score < thresh or nmax != None and len(result) == nmax - 1:
+ result.append(bz)
+ break
+ r = s1
+ l = s0 + .001 * (s1 - s0)
+ for i in range(10):
+ smid = 0.5 * (l + r)
+ zmid = curve.xy(smid)
+ bz, score = fit_cubic(z0, zmid, smid - s0, th_fn_inner, 0)
+ if score > thresh:
+ r = smid
+ else:
+ l = smid
+ print '% s0=', s0, 'smid=', smid, 'actual score =', score
+ result.append(bz)
+ s0 = smid
+ print '% last actual score=', score
+ return result, score
+
+def pcorn_segment_to_bzs_optim(curve, s0, s1, thresh, optim):
+ result, score = pcorn_segment_to_bzs_optim_inner(curve, s0, s1, thresh)
+ bresult, bscore = result, score
+ if len(result) > 1 and optim > 2:
+ nmax = len(result)
+ gamma = 1./6
+ l = score
+ r = thresh
+ for i in range(5):
+ tmid = (0.5 * (l ** gamma + r ** gamma)) ** (1/gamma)
+ result, score = pcorn_segment_to_bzs_optim_inner(curve, s0, s1, tmid, nmax)
+ if score < tmid:
+ l = max(l, score)
+ r = tmid
+ else:
+ l = tmid
+ r = min(r, score)
+ if max(score, tmid) < bscore:
+ bresult, bscore = result, max(score, tmid)
+ return result
+
+def pcorn_segment_to_bzs(curve, s0, s1, optim = 0, thresh = 1e-3):
+ if optim >= 2:
+ return pcorn_segment_to_bzs_optim(curve, s0, s1, thresh, optim)
+ z0 = curve.xy(s0)
+ z1 = curve.xy(s1)
+ fast = (optim == 0)
+ def th_fn(s):
+ return curve.th(s0 + s, s == 0)
+ bz, score = fit_cubic(z0, z1, s1 - s0, th_fn, fast)
+ if score < thresh:
+ return [bz]
+ else:
+ smid = 0.5 * (s0 + s1)
+ result = pcorn_segment_to_bzs(curve, s0, smid, optim, thresh)
+ result.extend(pcorn_segment_to_bzs(curve, smid, s1, optim, thresh))
+ return result
+
+def pcorn_curve_to_bzs(curve, optim = 3, thresh = 1e-3):
+ result = []
+ extrema = curve.find_extrema()
+ extrema.extend(curve.find_breaks())
+ extrema.sort()
+ print '%', extrema
+ for i in range(len(extrema)):
+ s0 = extrema[i]
+ if i == len(extrema) - 1:
+ s1 = extrema[0] + curve.arclen
+ else:
+ s1 = extrema[i + 1]
+ result.extend(pcorn_segment_to_bzs(curve, s0, s1, optim, thresh))
+ return result
+
+import struct
+
+def fit_cubic_arclen_forplot(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 * chord
+ darmlen = 1e-6 * armlen
+ for i in range(10):
+ 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]
+ actual_s = bz_arclength_rk4(bz)
+ if (abs(arclen - actual_s) < 1e-12):
+ break
+ a = (armlen + darmlen) * aab
+ b = (armlen + darmlen) - a
+ bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a),
+ (z1[0] + cth1 * b, z1[1] + sth1 * b), z1]
+ actual_s2 = bz_arclength_rk4(bz)
+ ds = (actual_s2 - actual_s) / darmlen
+ #print '% armlen = ', armlen
+ armlen += (arclen - actual_s) / ds
+ 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, a, b
+
+def plot_errors_2d(t0, t1, as_ppm):
+ xs = 1024
+ ys = 1024
+ if as_ppm:
+ print 'P6'
+ print xs, ys
+ print 255
+ def th_fn(s):
+ return (s + t0) ** 2
+ y0, x0 = cornu.eval_cornu(t0)
+ y1, x1 = cornu.eval_cornu(t1)
+ z0 = (x0, y0)
+ z1 = (x1, y1)
+ chord = hypot(y1 - y0, x1 - x0)
+ arclen = t1 - t0
+ th0 = th_fn(0)
+ th1 = th_fn(arclen)
+ cth0, sth0 = cos(th0), sin(th0)
+ cth1, sth1 = -cos(th1), -sin(th1)
+
+ for y in range(ys):
+ b = .8 * chord * (ys - y - 1) / ys
+ for x in range(xs):
+ a = .8 * chord * x / xs
+ bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a),
+ (z1[0] + cth1 * b, z1[1] + sth1 * b), z1]
+ s_bz = bz_arclength(bz, 10)
+ def th_fn_scaled(s):
+ return (s * arclen / s_bz + t0) ** 2
+ score = measure_bz_rk4(bz, arclen, th_fn_scaled, 10)
+ if as_ppm:
+ ls = -log(score)
+ color_th = ls
+ darkstep = 0
+ if s_bz > arclen:
+ g0 = 128 - darkstep
+ else:
+ g0 = 128 + darkstep
+ sc = 127 - darkstep
+ rr = g0 + sc * cos(color_th)
+ gg = g0 + sc * cos(color_th + 2 * pi / 3)
+ bb = g0 + sc * cos(color_th - 2 * pi / 3)
+ sys.stdout.write(struct.pack('3B', rr, gg, bb))
+ else:
+ print a, b, score
+ if not as_ppm:
+ print
+
+def plot_arclen(t0, t1):
+ def th_fn(s):
+ return (s + t0) ** 2
+ y0, x0 = cornu.eval_cornu(t0)
+ y1, x1 = cornu.eval_cornu(t1)
+ z0 = (x0, y0)
+ z1 = (x1, y1)
+ chord = hypot(y1 - y0, x1 - x0)
+ arclen = t1 - t0
+ th0 = th_fn(0)
+ th1 = th_fn(arclen)
+ for i in range(101):
+ aab = i * .01
+ bz, a, b = fit_cubic_arclen_forplot(z0, z1, arclen, th0, th1, aab)
+ print a, b
+
+if __name__ == '__main__':
+ #test_bz_arclength()
+ test_draw_cornu()
+ #run_one_cornu_seg()
+ #plot_errors_2d(.5, 1.0, False)
+ #plot_arclen(.5, 1.0)
diff --git a/third_party/spiro/font/Makefile b/third_party/spiro/font/Makefile
new file mode 100644
index 0000000..f6096a6
--- /dev/null
+++ b/third_party/spiro/font/Makefile
@@ -0,0 +1,3 @@
+CFLAGS = -Wall -O2 -g
+LDLIBS = -lm
+all: blend segment
diff --git a/third_party/spiro/font/blend.c b/third_party/spiro/font/blend.c
new file mode 100644
index 0000000..87a540f
--- /dev/null
+++ b/third_party/spiro/font/blend.c
@@ -0,0 +1,372 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+typedef struct {
+ const char *fn;
+ int xs;
+ int ys;
+ unsigned char *buf;
+} pgm;
+
+typedef struct {
+ int xs;
+ int ys;
+ int *sum;
+ int *count;
+} blendbuf;
+
+static void
+die (char *why)
+{
+ fprintf (stderr, "%s\n", why);
+ exit (1);
+}
+
+#define MAX_SIZE 65536
+
+pgm *load_pgm(const char *fn)
+{
+ FILE *fi = fopen(fn, "rb");
+ pgm *result;
+ char buf[256];
+ int xs, ys;
+ int depth;
+
+ if (fi == NULL)
+ return NULL;
+
+ fgets (buf, sizeof(buf), fi);
+ if (buf[0] != 'P' || buf[1] != '5')
+ die ("Need pgmraw image on input");
+
+ xs = ys = 0;
+ do
+ fgets (buf, sizeof(buf), fi);
+ while (buf[0] == '#');
+ sscanf (buf, "%d %d", &xs, &ys);
+ if (xs <= 0 || ys <= 0 || xs > MAX_SIZE || ys > MAX_SIZE)
+ die ("Input image size out of range");
+
+ do
+ fgets (buf, sizeof(buf), fi);
+ while (buf[0] == '#');
+ sscanf (buf, "%d", &depth);
+ if (depth != 255)
+ die ("Only works with depth=255 images");
+
+ result = (pgm *)malloc(sizeof(pgm));
+
+ result->fn = fn;
+ result->xs = xs;
+ result->ys = ys;
+ result->buf = (unsigned char *)malloc(xs * ys);
+
+ fread(result->buf, 1, xs * ys, fi);
+ fprintf(stderr, "loaded file %s %dx%d\n", fn, xs, ys);
+ fclose(fi);
+ return result;
+}
+
+int
+align_pgms(const pgm *p1, const pgm *p2, int *px, int *py)
+{
+ int xo, yo;
+ int xa, ya;
+ int best = 0x7fffffff;
+
+ xa = (p1->xs < p2->xs ? p1->xs : p2->xs) - 20;
+ ya = (p1->ys < p2->ys ? p1->ys : p2->ys) - 20;
+
+ for (yo = -10; yo <= 10; yo++)
+ for (xo = -10; xo <= 10; xo++) {
+ int sum = 0;
+ int i, j;
+
+ for (j = 0; j < ya; j++)
+ for (i = 0; i < xa; i++) {
+ int g1 = p1->buf[(j + 10) * p1->xs + i + 10];
+ int g2 = p2->buf[(j + 10 - yo) * p2->xs + i - xo + 10];
+ sum += (g1 - g2) * (g1 - g2);
+ }
+ if (sum < best) {
+ best = sum;
+ *px = xo;
+ *py = yo;
+ }
+ }
+ return best;
+}
+
+blendbuf *
+new_blendbuf(int xs, int ys)
+{
+ blendbuf *result = (blendbuf *)malloc(sizeof(blendbuf));
+ int i;
+
+ result->xs = xs;
+ result->ys = ys;
+ result->sum = (int *)malloc(sizeof(int) * xs * ys);
+ result->count = (int *)malloc(sizeof(int) * xs * ys);
+ for (i = 0; i < xs * ys; i++) {
+ result->sum[i] = 0;
+ result->count[i] = 0;
+ }
+
+ return result;
+}
+
+void
+add_pgm(blendbuf *bb, pgm *p, int xo, int yo)
+{
+ int i, j;
+
+ for (j = 0; j < p->ys; j++) {
+ if (j + yo >= 0 && j + yo < bb->ys) {
+ for (i = 0; i < p->xs; i++) {
+ if (i + xo >= 0 && i + xo < bb->xs) {
+ int ix = (j + yo) * bb->xs + i + xo;
+ bb->sum[ix] += p->buf[j * p->xs + i];
+ bb->count[ix]++;
+ }
+ }
+ }
+ }
+}
+
+pgm *
+pgm_from_blendbuf(blendbuf *bb)
+{
+ int xs = bb->xs;
+ int ys = bb->ys;
+ pgm *result = (pgm *)malloc(sizeof(pgm));
+ int i, j;
+
+ result->xs = xs;
+ result->ys = ys;
+ result->buf = (unsigned char *)malloc(xs * ys);
+
+ for (j = 0; j < ys; j++) {
+ for (i = 0; i < xs; i++) {
+ int ix = j * xs + i;
+ unsigned char g;
+ if (bb->count[ix])
+ g = (bb->sum[ix] + (bb->count[ix] >> 1)) / bb->count[ix];
+ else
+ g = 255;
+ result->buf[ix] = g;
+ }
+ }
+ return result;
+}
+
+pgm *
+pgm_from_blendbuf_enhanced(blendbuf *bb, double sharp, double gamma)
+{
+ int xs = bb->xs;
+ int ys = bb->ys;
+ pgm *result = (pgm *)malloc(sizeof(pgm));
+ int i, j;
+ double ming = 255, maxg = 0;
+ double *tmpbuf;
+
+ result->xs = xs;
+ result->ys = ys;
+ result->buf = (unsigned char *)malloc(xs * ys);
+
+ tmpbuf = (double *)malloc(xs * ys * sizeof(double));
+
+ for (j = 0; j < ys; j++) {
+ for (i = 0; i < xs; i++) {
+ int ix = j * xs + i;
+ double g;
+ if (bb->count[ix]) {
+ g = ((double)bb->sum[ix]) / bb->count[ix];
+ if (g < ming) ming = g;
+ if (g > maxg) maxg = g;
+ } else
+ g = 255.0;
+ tmpbuf[ix] = g;
+ }
+ }
+ for (j = 0; j < ys; j++) {
+ for (i = 0; i < xs; i++) {
+ int ix = j * xs + i;
+ double g;
+ if (bb->count[ix]) {
+ int u, v;
+ int cnt = 0;
+ double sum = 0;
+
+ for (v = -1; v <= 1; v++) {
+ for (u = -1; u <= 1; u++) {
+ if (i + u >= 0 && i + u < xs &&
+ j + v >= 0 && j + v < ys &&
+ bb->count[(j + v) * xs + i + u]) {
+ sum += tmpbuf[(j + v) * xs + i + u];
+ cnt += 1;
+ }
+ }
+ }
+
+ g = (1 + sharp) * tmpbuf[ix] - sharp * sum / cnt;
+ g = (g - ming) / (maxg - ming);
+ if (g < 0) g = 0;
+ if (g > 1) g = 1;
+ g = pow(g, gamma);
+ } else
+ g = 1;
+ result->buf[ix] = (int)(255 * g + 0.5);
+ }
+ }
+ free(tmpbuf);
+ return result;
+}
+
+void
+print_pgm(pgm *p, FILE *f)
+{
+ fprintf(f, "P5\n%d %d\n255\n", p->xs, p->ys);
+ fwrite(p->buf, 1, p->xs * p->ys, f);
+}
+
+void
+threshold(pgm *p)
+{
+ int i;
+
+ for (i = 0; i < p->xs * p->ys; i++)
+ p->buf[i] = p->buf[i] > 128 ? 1 : 0;
+}
+
+int
+classify(pgm **pgmlist, int n_pgm)
+{
+ int *class = (int *)malloc(sizeof(int) * n_pgm);
+ int n_class = 0;
+ int i, j;
+ int tshift = 4;
+
+ for (i = 0; i < n_pgm; i++)
+ class[i] = -1;
+
+ for (i = 0; i < n_pgm; i++) {
+ pgm *pi = pgmlist[i];
+
+ if (class[i] == -1) {
+ class[i] = n_class++;
+ for (j = i + 1; j < n_pgm; j++) {
+ pgm *pj = pgmlist[j];
+ int xo, yo;
+ int score;
+
+ if (abs(pi->xs - pj->xs) < 10 &&
+ abs(pi->ys - pj->ys) < 10) {
+ score = align_pgms(pi, pj, &xo, &yo);
+ if (score < ((pi->xs - 20) * (pi->ys - 20)) >> tshift) {
+ class[j] = class[i];
+ }
+ }
+ }
+ }
+ printf("%s: class%d\n", pi->fn, class[i]);
+ fflush(stdout);
+ }
+ free(class);
+ return 0;
+}
+
+
+static int
+intcompar(const void *a, const void *b) {
+ return *((int *)a) - *((int *)b);
+}
+
+int
+do_blend(pgm **pgmlist, int n_pgm, int n_passes, float thresh)
+{
+ blendbuf *bb;
+ int pass;
+ int i;
+ pgm *base = pgmlist[0];
+ int *scores, *scores2, *xos, *yos;
+
+ scores = (int *)malloc(n_pgm * sizeof(int));
+ scores2 = (int *)malloc(n_pgm * sizeof(int));
+ xos = (int *)malloc(n_pgm * sizeof(int));
+ yos = (int *)malloc(n_pgm * sizeof(int));
+
+ for (pass = 0; pass < n_passes; pass++) {
+ int scorethresh;
+
+ bb = new_blendbuf(base->xs, base->ys);
+ for (i = 0; i < n_pgm; i++) {
+ int xo, yo;
+ int score = align_pgms(base, pgmlist[i], &xo, &yo);
+ fprintf(stderr, "%s: score = %d, offset = %d, %d\n",
+ pgmlist[i]->fn, score, xo, yo);
+ scores[i] = score;
+ xos[i] = xo;
+ yos[i] = yo;
+ }
+
+ if (pass == 0) {
+ scorethresh = 0x7fffffff;
+ } else {
+ memcpy(scores2, scores, n_pgm * sizeof(int));
+ qsort(scores2, n_pgm, sizeof(int), intcompar);
+ scorethresh = scores2[(int)(thresh * n_pgm)];
+ }
+
+ for (i = 0; i < n_pgm; i++) {
+ if (pass > 0)
+ fprintf(stderr, "%s: score = %d %s\n",
+ pgmlist[i]->fn, scores[i],
+ scores[i] <= scorethresh ? "ok" : "-");
+ if (scores[i] <= scorethresh)
+ add_pgm(bb, pgmlist[i], xos[i], yos[i]);
+ }
+
+ if (pass == n_passes - 1)
+ base = pgm_from_blendbuf_enhanced(bb, 5, 0.5);
+ else
+ base = pgm_from_blendbuf(bb);
+ if (pass != n_passes - 1)
+ fprintf(stderr, "\n");
+ }
+
+ free(scores);
+ free(xos);
+ free(yos);
+ print_pgm(base, stdout);
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ int i;
+ int n_pgm = 0;
+ pgm **pgmlist = (pgm **)malloc(sizeof(pgm *) * argc - 1);
+ int do_class = 0;
+ int n_pass = 2;
+ float thresh = 0.90;
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-c"))
+ do_class = 1;
+ else
+ pgmlist[n_pgm++] = load_pgm(argv[i]);
+ }
+
+ if (do_class) {
+ for (i = 0; i < n_pgm; i++)
+ threshold(pgmlist[i]);
+ return classify(pgmlist, n_pgm);
+ } else {
+ return do_blend(pgmlist, n_pgm, n_pass, thresh);
+ }
+
+ return 0;
+}
diff --git a/third_party/spiro/font/cut.py b/third_party/spiro/font/cut.py
new file mode 100644
index 0000000..541efa8
--- /dev/null
+++ b/third_party/spiro/font/cut.py
@@ -0,0 +1,52 @@
+import sys
+
+athresh = 100
+border = 20
+
+segf = sys.argv[1]
+if len(sys.argv) > 2:
+ pref = sys.argv[2]
+else:
+ pref = '/tmp/cut'
+rects = []
+starts = {}
+for l in file(segf).xreadlines():
+ ls = l.split()
+ if len(ls) == 6 and ls[-1] == 'rect':
+ r = map(int, ls[:4])
+ area = (r[2] - r[0]) * (r[3] - r[1])
+ if area > athresh:
+ rpad = [r[0] - border, r[1] - border, r[2] + border, r[3] + border]
+ if not starts.has_key(rpad[1]):
+ starts[rpad[1]] = []
+ starts[rpad[1]].append(len(rects))
+ rects.append(rpad)
+inf = sys.stdin
+l = inf.readline()
+if l != 'P5\n':
+ raise 'expected pgm file'
+while 1:
+ l = inf.readline()
+ if l[0] != '#': break
+x, y = map(int, l.split())
+l = inf.readline()
+
+active = {}
+for j in range(y):
+ if starts.has_key(j):
+ for ix in starts[j]:
+ r = rects[ix]
+ ofn = pref + '%04d.pgm' % ix
+ of = file(ofn, 'w')
+ active[ix] = of
+ print >> of, 'P5'
+ print >> of, r[2] - r[0], r[3] - r[1]
+ print >> of, '255'
+ buf = inf.read(x)
+ for ix, of in active.items():
+ r = rects[ix]
+ of.write(buf[r[0]:r[2]])
+ if j == r[3] - 1:
+ of.close()
+ del active[ix]
+
diff --git a/third_party/spiro/font/mkblends.py b/third_party/spiro/font/mkblends.py
new file mode 100644
index 0000000..09fe882
--- /dev/null
+++ b/third_party/spiro/font/mkblends.py
@@ -0,0 +1,16 @@
+import os, sys
+
+glyphmap = {}
+for ln in file(sys.argv[1]).xreadlines():
+ fnglyph = ln.strip().split(': ')
+ if len(fnglyph) == 2:
+ fn, name = fnglyph
+ pgmf = fn[:-4] + '.pgm'
+ if not glyphmap.has_key(name):
+ glyphmap[name] = []
+ glyphmap[name].append(pgmf)
+for name in glyphmap.iterkeys():
+ cmd = '~/garden/font/blend ' + ' '.join(glyphmap[name]) + ' | pnmtopng > ' + name + '.png'
+ print cmd
+ os.system(cmd)
+
diff --git a/third_party/spiro/font/replace_class.py b/third_party/spiro/font/replace_class.py
new file mode 100644
index 0000000..9a1da51
--- /dev/null
+++ b/third_party/spiro/font/replace_class.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+
+import sys
+srfile = file(sys.argv[1])
+table = {}
+for line in srfile.xreadlines():
+ clas, repl = line.split()
+ if repl[-1] not in '-?':
+ table['class' + clas] = repl
+
+for line in sys.stdin.xreadlines():
+ fn, clas = line.split()
+ if table.has_key(clas):
+ print fn, table[clas]
diff --git a/third_party/spiro/font/segment.c b/third_party/spiro/font/segment.c
new file mode 100644
index 0000000..8eb6b06
--- /dev/null
+++ b/third_party/spiro/font/segment.c
@@ -0,0 +1,194 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+
+typedef struct {
+ int x0;
+ int x1;
+ int y0;
+ int y1;
+ double xmom;
+ double ymom;
+ int area;
+} seg;
+
+typedef struct {
+ int x0;
+ int x1;
+} run;
+
+void
+find_runs(run *result, const unsigned char *buf, unsigned char thresh, int xs)
+{
+ int x;
+ int j = 0;
+
+ for (x = 0; x < xs;) {
+ int x0, x1;
+ for (x0 = x; x0 < xs; x0++)
+ if (buf[x0] < thresh)
+ break;
+ if (x0 == xs)
+ break;
+ for (x1 = x0 + 1; x1 < xs; x1++)
+ if (buf[x1] >= thresh)
+ break;
+ result[j].x0 = x0;
+ result[j].x1 = x1;
+ j++;
+ x = x1 + 1;
+ }
+ result[j].x0 = 0;
+ result[j].x1 = 0;
+}
+
+void
+print_rect(const seg *r)
+{
+ printf("%d %d %d %d %g rect\n", r->x0, r->y0, r->x1, r->y1,
+ r->area / (1.0 * (r->x1 - r->x0) * (r->y1 - r->y0)));
+ printf("%g %g ci\n", r->xmom / r->area, r->ymom / r->area);
+}
+
+void
+merge_runs(seg *buf, const run *new_runs, int y)
+{
+ int bi, ni;
+ int bs;
+ int bi0;
+ int flag;
+
+ for (bs = 0; buf[bs].x1; bs++);
+
+ bi = 0;
+ flag = 1;
+ for (ni = 0; new_runs[ni].x1; ni++) {
+ int run_len = new_runs[ni].x1 - new_runs[ni].x0;
+
+ bi0 = bi;
+ for (; bi < bs && buf[bi].x1 <= new_runs[ni].x0; bi++) {
+ if (flag) {
+ buf[bi].y1 = y;
+ print_rect(&buf[bi]);
+ } else {
+ bi0 = bi + 1;
+ flag = 1;
+ }
+ }
+ if (bi > bi0) {
+ memmove(&buf[bi0], &buf[bi], (bs - bi) * sizeof(seg));
+ bs += bi0 - bi;
+ }
+ bi = bi0;
+ if (bi < bs && buf[bi].x0 < new_runs[ni].x1) {
+ double xmom = buf[bi].xmom;
+ double ymom = buf[bi].ymom;
+ int area = buf[bi].area;
+ int y0 = buf[bi].y0;
+
+ for (bi = bi + 1; bi < bs && buf[bi].x0 < new_runs[ni].x1; bi++) {
+ y0 = MIN(y0, buf[bi].y0);
+ xmom += buf[bi].xmom;
+ ymom += buf[bi].ymom;
+ area += buf[bi].area;
+ }
+ buf[bi0].x0 = MIN(buf[bi0].x0, new_runs[ni].x0);
+ buf[bi0].x1 = MAX(buf[bi - 1].x1, new_runs[ni].x1);
+ buf[bi0].y0 = y0;
+ buf[bi0].xmom = xmom + run_len * .5 * (new_runs[ni].x0 + new_runs[ni].x1);
+ buf[bi0].ymom = ymom + run_len * y;
+ buf[bi0].area = area + run_len;
+ if (bi > bi0 + 1) {
+ memmove(&buf[bi0 + 1], &buf[bi], (bs - bi) * sizeof(seg));
+ bs += bi0 + 1 - bi;
+ }
+ bi = bi0;
+ flag = 0;
+ } else {
+ memmove(&buf[bi + 1], &buf[bi], (bs - bi) * sizeof(seg));
+ bs++;
+ buf[bi].x0 = new_runs[ni].x0;
+ buf[bi].x1 = new_runs[ni].x1;
+ buf[bi].y0 = y;
+ buf[bi].xmom = run_len * .5 * (new_runs[ni].x0 + new_runs[ni].x1);
+ buf[bi].ymom = run_len * y;
+ buf[bi].area = run_len;
+ bi++;
+ flag = 1;
+ }
+ }
+ bi0 = bi;
+ for (; bi < bs; bi++) {
+ if (flag) {
+ buf[bi].y1 = y;
+ print_rect(&buf[bi]);
+ } else {
+ bi0 = bi + 1;
+ flag = 1;
+ }
+ }
+ buf[bi0].x0 = 0;
+ buf[bi0].x1 = 0;
+}
+
+static void
+die (char *why)
+{
+ fprintf (stderr, "%s\n", why);
+ exit (1);
+}
+
+#define MAX_SIZE 65536
+
+int
+main (int argc, char **argv)
+{
+ FILE *fi = stdin;
+ FILE *fo = stdout;
+ char buf[256];
+ int xs, ys;
+ int depth;
+ unsigned char *imgbuf;
+ seg *segs;
+ run *runs;
+ int y;
+
+ fgets (buf, sizeof(buf), fi);
+ if (buf[0] != 'P' || buf[1] != '5')
+ die ("Need pgmraw image on input");
+
+ xs = ys = 0;
+ do
+ fgets (buf, sizeof(buf), fi);
+ while (buf[0] == '#');
+ sscanf (buf, "%d %d", &xs, &ys);
+ if (xs <= 0 || ys <= 0 || xs > MAX_SIZE || ys > MAX_SIZE)
+ die ("Input image size out of range");
+
+ do
+ fgets (buf, sizeof(buf), fi);
+ while (buf[0] == '#');
+ sscanf (buf, "%d", &depth);
+ if (depth != 255)
+ die ("Only works with depth=255 images");
+
+ runs = (run *)malloc(((xs + 3) >> 1) * sizeof(run));
+ segs = (seg *)malloc(((xs + 3) >> 1) * sizeof(seg));
+ imgbuf = (unsigned char *)malloc (xs);
+ segs[0].x0 = 0;
+ segs[0].x1 = 0;
+ for (y = 0; y < ys; y++) {
+ fread (imgbuf, 1, xs, fi);
+ find_runs(runs, imgbuf, 160, xs);
+ merge_runs(segs, runs, y);
+ }
+
+ free(imgbuf);
+ free(runs);
+ free(segs);
+
+ return 0;
+}
diff --git a/third_party/spiro/ppedit/Makefile b/third_party/spiro/ppedit/Makefile
new file mode 100644
index 0000000..589fdc1
--- /dev/null
+++ b/third_party/spiro/ppedit/Makefile
@@ -0,0 +1,25 @@
+TARGET = gtk
+
+ifeq ($(TARGET),gtk)
+ X3_PLAT = X3_GTK
+ X3_INCL = `pkg-config --cflags gtk+-2.0`
+ X3_LIBS = `pkg-config --libs gtk+-2.0`
+endif
+
+ifeq ($(TARGET),carbon)
+ X3_PLAT = X3_CARBON
+ X3_LIBS = -framework Carbon
+endif
+
+ifeq ($(TARGET),win32)
+ X3_PLAT = X3_WIN32
+ X3_LIBS = -lgdi32
+endif
+
+CFLAGS = -g -Wall -D$(X3_PLAT) $(X3_INCL) -I../x3/
+LDFLAGS = -g
+LDLIBS = $(X3_LIBS)
+
+all: ppedit
+
+ppedit: ppedit.o cornu.o bezctx.o bezctx_x3.o bezctx_hittest.o plate.o sexp.o image.o bezctx_ps.o spiro.o ../x3/x3$(TARGET).o ../x3/x3common.o
diff --git a/third_party/spiro/ppedit/Makefile_gtk1 b/third_party/spiro/ppedit/Makefile_gtk1
new file mode 100644
index 0000000..9523375
--- /dev/null
+++ b/third_party/spiro/ppedit/Makefile_gtk1
@@ -0,0 +1,5 @@
+CFLAGS = -Wall -g `gtk-config --cflags` `libart2-config --cflags`
+LDLIBS = `gtk-config --libs` `libart2-config --libs`
+all: ppedit_gtk1
+
+ppedit_gtk1: ppedit_gtk1.o cornu.o bezctx.o bezctx_libart.o bezctx_hittest.o plate.o sexp.o image.o bezctx_ps.o spiro.o
diff --git a/third_party/spiro/ppedit/README b/third_party/spiro/ppedit/README
new file mode 100644
index 0000000..c99037d
--- /dev/null
+++ b/third_party/spiro/ppedit/README
@@ -0,0 +1,112 @@
+README for ppedit
+
+Raph Levien
+4 May 2007
+
+ppedit is my prototype application for editing curves using my
+curvature-continuous spirals. While I have used this code to draw many
+font outlines, it is very rough around the edges, and is far from a
+polished tool.
+
+
+== License and patent grant ==
+
+All code in this package is released under the terms of the GNU GPL,
+version 2 or later, at your choice.
+
+Further, there is a provisional patent application filed for the
+underlying curve technology. The following patent grant applies to any
+patent which may be issued as a result of that application:
+
+Whereas, Raph Levien (hereinafter "Inventor") has obtained patent
+protection for related technology (hereinafter "Patented Technology"),
+Inventor wishes to aid the the GNU free software project in achieving
+its goals, and Inventor also wishes to increase public awareness of
+Patented Technology, Inventor hereby grants a fully paid up,
+nonexclusive, irrevocable, royalty free license to practice the
+patents listed below ("the Patents") if and only if practiced in
+conjunction with software distributed under the terms of any version
+of the GNU General Public License as published by the Free Software
+Foundation, 59 Temple Place, Suite 330, Boston, MA 02111. Inventor
+reserves all other rights, including without limitation, licensing for
+software not distributed under the GNU General Public License.
+
+== Building ==
+
+The main build supported right now is the Gtk2/cairo one. There's also
+a Mac build and a Gtk1 one, but those aren't guaranteed to work.
+
+1. Make sure you've got ../x3/ in a directory parallel to ppedit. If
+ you've unpacked from a tarball, this should be the case already.
+ From darcs, use: darcs get http://levien.com/garden/x3
+
+2. make
+
+3. The binary is ppedit
+
+== Using ==
+
+The numeric keys 1-6 select the mode. 1 is selection, 2-6 select
+different point modes:
+
+2: Add G4-continuous curve point
+3: Add corner point
+4: Add left-facing one-way point
+5: Add right-facing one-way point
+6: Add G2-continuous curve point
+
+Note: Dave Crossland has a set of alternate keybindings which are
+probably faster.
+
+== Plate files ==
+
+Ctrl-S saves a plate file in a file of the name 'plate'. Additionally,
+a plate file can be given as a command line argument. The file uses
+simple S-expressions, with a one-character code for each point, then
+the X and Y coordinates - 0,0 is top left.
+
+Here's the cap U from Inconsolata, for example:
+
+(plate
+ (v 68 78)
+ (v 159 78)
+ (o 158 92)
+ ([ 148 115)
+ (] 148 552)
+ (o 298 744)
+ ([ 459 549)
+ (v 459 78)
+ (v 536 78)
+ (] 536 547)
+ (o 295 813)
+ ([ 68 551)
+ (z)
+)
+
+v: corner
+o: g4
+c: g2
+[: left-facing one-way
+]: right-facing one-way
+
+== Conversion to PostScript ==
+
+Ctrl-P converts to PostScript, saving '/tmp/foo.ps'. Other utilities
+can convert that representation into FontForge, and also optimize the
+Beziers.
+
+== Stability ==
+
+The spline solver in this release is _not_ numerically robust. When
+you start drawing random points, you'll quickly run into divergence.
+However, "sensible" plates based on real fonts usually converge. Some
+tips:
+
+1. Huge changes of angle are likely to diverge.
+
+2. For the first two or three points, G4 points are likelier to
+ converge than G2's. For longer segments, G2 is more likely.
+
+3. Start on a curve point.
+
+A more numerically robust approach is in the works.
diff --git a/third_party/spiro/ppedit/bezctx.c b/third_party/spiro/ppedit/bezctx.c
new file mode 100644
index 0000000..722f5db
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx.c
@@ -0,0 +1,48 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include "bezctx.h"
+
+void bezctx_moveto(bezctx *bc, double x, double y, int is_open)
+{
+ bc->moveto(bc, x, y, is_open);
+}
+
+void bezctx_lineto(bezctx *bc, double x, double y)
+{
+ bc->lineto(bc, x, y);
+}
+
+void bezctx_quadto(bezctx *bc, double x1, double y1, double x2, double y2)
+{
+ bc->quadto(bc, x1, y1, x2, y2);
+}
+
+void bezctx_curveto(bezctx *bc, double x1, double y1, double x2, double y2,
+ double x3, double y3)
+{
+ bc->curveto(bc, x1, y1, x2, y2, x3, y3);
+}
+
+void bezctx_mark_knot(bezctx *bc, int knot_idx)
+{
+ if (bc->mark_knot)
+ bc->mark_knot(bc, knot_idx);
+}
diff --git a/third_party/spiro/ppedit/bezctx.h b/third_party/spiro/ppedit/bezctx.h
new file mode 100644
index 0000000..057312e
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx.h
@@ -0,0 +1,10 @@
+#include "bezctx_intf.h"
+
+struct _bezctx {
+ void (*moveto)(bezctx *bc, double x, double y, int is_open);
+ void (*lineto)(bezctx *bc, double x, double y);
+ void (*quadto)(bezctx *bc, double x1, double y1, double x2, double y2);
+ void (*curveto)(bezctx *bc, double x1, double y1, double x2, double y2,
+ double x3, double y3);
+ void (*mark_knot)(bezctx *bc, int knot_idx);
+};
diff --git a/third_party/spiro/ppedit/bezctx_hittest.c b/third_party/spiro/ppedit/bezctx_hittest.c
new file mode 100644
index 0000000..5a3d549
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx_hittest.c
@@ -0,0 +1,261 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include "zmisc.h"
+#include "bezctx.h"
+#include "bezctx_hittest.h"
+#include <math.h>
+
+typedef struct {
+ bezctx base;
+ double x, y;
+
+ double x0, y0;
+ int knot_idx;
+
+ int knot_idx_min;
+ double r_min;
+} bezctx_hittest;
+
+static void
+bezctx_hittest_moveto(bezctx *z, double x, double y, int is_open) {
+ bezctx_hittest *bc = (bezctx_hittest *)z;
+
+ bc->x0 = x;
+ bc->y0 = y;
+}
+
+static void
+bezctx_hittest_lineto(bezctx *z, double x, double y) {
+ bezctx_hittest *bc = (bezctx_hittest *)z;
+ double x0 = bc->x0;
+ double y0 = bc->y0;
+ double dx = x - x0;
+ double dy = y - y0;
+ double dotp = (bc->x - x0) * dx + (bc->y - y0) * dy;
+ double lin_dotp = dx * dx + dy * dy;
+ double r_min, r;
+
+ r = hypot(bc->x - x0, bc->y - y0);
+ r_min = r;
+ r = hypot(bc->x - x, bc->y - y);
+ if (r < r_min) r_min = r;
+
+ if (dotp >= 0 && dotp <= lin_dotp) {
+ double norm = (bc->x - x0) * dy - (bc->y - y0) * dx;
+ r = fabs(norm / sqrt(lin_dotp));
+ if (r < r_min) r_min = r;
+ }
+
+ if (r_min < bc->r_min) {
+ bc->r_min = r_min;
+ bc->knot_idx_min = bc->knot_idx;
+ }
+
+ bc->x0 = x;
+ bc->y0 = y;
+}
+
+#define cube(x) ((x) * (x) * (x))
+
+static double
+my_cbrt(double x)
+{
+ if (x >= 0)
+ return pow(x, 1.0 / 3.0);
+ else
+ return -pow(-x, 1.0 / 3.0);
+}
+
+/**
+ * Give real roots to eqn c0 + c1 * x + c2 * x^2 + c3 * x^3 == 0.
+ * Return value is number of roots found.
+ **/
+static int
+solve_cubic(double c0, double c1, double c2, double c3, double root[3])
+{
+ double p, q, r, a, b, Q, x0;
+
+ p = c2 / c3;
+ q = c1 / c3;
+ r = c0 / c3;
+ a = (3 * q - p * p) / 3;
+ b = (2 * cube(p) - 9 * p * q + 27 * r) / 27;
+ Q = b * b / 4 + cube(a) / 27;
+ x0 = p / 3;
+ if (Q > 0) {
+ double sQ = sqrt(Q);
+ double t1 = my_cbrt(-b/2 + sQ) + my_cbrt(-b/2 - sQ);
+ root[0] = t1 - x0;
+ return 1;
+ } else if (Q == 0) {
+ double t1 = my_cbrt(b / 2);
+ double x1 = t1 - x0;
+ root[0] = x1;
+ root[1] = x1;
+ root[2] = -2 * t1 - x0;
+ return 3;
+ } else {
+ double sQ = sqrt(-Q);
+ double rho = hypot(-b/2, sQ);
+ double th = atan2(sQ, -b/2);
+ double cbrho = my_cbrt(rho);
+ double c = cos(th / 3);
+ double s = sin(th / 3);
+ double sqr3 = sqrt(3);
+ root[0] = 2 * cbrho * c - x0;
+ root[1] = -cbrho * (c + sqr3 * s) - x0;
+ root[2] = -cbrho * (c - sqr3 * s) - x0;
+ return 3;
+ }
+}
+
+
+static double
+dist_to_quadratic(double x, double y,
+ double x0, double y0,
+ double x1, double y1,
+ double x2, double y2)
+{
+ double u0, u1, t0, t1, t2, c0, c1, c2, c3;
+ double roots[3];
+ int n_roots;
+ double ts[4];
+ int n_ts;
+ int i;
+ double minerr = 0;
+
+ u0 = x1 - x0;
+ u1 = x0 - 2 * x1 + x2;
+ t0 = x0 - x;
+ t1 = 2 * u0;
+ t2 = u1;
+ c0 = t0 * u0;
+ c1 = t1 * u0 + t0 * u1;
+ c2 = t2 * u0 + t1 * u1;
+ c3 = t2 * u1;
+
+ u0 = y1 - y0;
+ u1 = y0 - 2 * y1 + y2;
+ t0 = y0 - y;
+ t1 = 2 * u0;
+ t2 = u1;
+ c0 += t0 * u0;
+ c1 += t1 * u0 + t0 * u1;
+ c2 += t2 * u0 + t1 * u1;
+ c3 += t2 * u1;
+
+ n_roots = solve_cubic(c0, c1, c2, c3, roots);
+ n_ts = 0;
+ for (i = 0; i < n_roots; i++) {
+ double t = roots[i];
+ if (t > 0 && t < 1)
+ ts[n_ts++] = t;
+ }
+ if (n_ts < n_roots) {
+ ts[n_ts++] = 0;
+ ts[n_ts++] = 1;
+ }
+ for (i = 0; i < n_ts; i++) {
+ double t = ts[i];
+ double xa = x0 * (1 - t) * (1 - t) + 2 * x1 * (1 - t) * t + x2 * t * t;
+ double ya = y0 * (1 - t) * (1 - t) + 2 * y1 * (1 - t) * t + y2 * t * t;
+ double err = hypot(xa - x, ya - y);
+ if (i == 0 || err < minerr) {
+ minerr = err;
+ }
+ }
+ return minerr;
+}
+
+static void
+bezctx_hittest_quadto(bezctx *z, double x1, double y1, double x2, double y2)
+{
+ bezctx_hittest *bc = (bezctx_hittest *)z;
+ double r = dist_to_quadratic(bc->x, bc->y,
+ bc->x0, bc->y0, x1, y1, x2, y2);
+
+ if (r < bc->r_min) {
+ bc->r_min = r;
+ bc->knot_idx_min = bc->knot_idx;
+ }
+ bc->x0 = x2;
+ bc->y0 = y2;
+}
+
+static void
+bezctx_hittest_curveto(bezctx *z, double x1, double y1, double x2, double y2,
+ double x3, double y3)
+{
+ bezctx_hittest *bc = (bezctx_hittest *)z;
+ double x0 = bc->x0;
+ double y0 = bc->y0;
+ int n_subdiv = 32;
+ int i;
+ double xq2, yq2;
+
+ /* todo: subdivide to quadratics rather than lines */
+ for (i = 0; i < n_subdiv; i++) {
+ double t = (1. / n_subdiv) * (i + 1);
+ double mt = 1 - t;
+
+ xq2 = x0 * mt * mt * mt + 3 * x1 * mt * t * t + 3 * x2 * mt * mt * t +
+ x3 * t * t * t;
+ yq2 = y0 * mt * mt * mt + 3 * y1 * mt * t * t + 3 * y2 * mt * mt * t +
+ y3 * t * t * t;
+ bezctx_hittest_lineto(z, xq2, yq2);
+ }
+}
+
+static void
+bezctx_hittest_mark_knot(bezctx *z, int knot_idx) {
+ bezctx_hittest *bc = (bezctx_hittest *)z;
+
+ bc->knot_idx = knot_idx;
+}
+
+bezctx *
+new_bezctx_hittest(double x, double y) {
+ bezctx_hittest *result = znew(bezctx_hittest, 1);
+
+ result->base.moveto = bezctx_hittest_moveto;
+ result->base.lineto = bezctx_hittest_lineto;
+ result->base.quadto = bezctx_hittest_quadto;
+ result->base.curveto = bezctx_hittest_curveto;
+ result->base.mark_knot = bezctx_hittest_mark_knot;
+ result->x = x;
+ result->y = y;
+ result->knot_idx_min = -1;
+ result->r_min = 1e12;
+ return &result->base;
+}
+
+double
+bezctx_hittest_report(bezctx *z, int *p_knot_idx)
+{
+ bezctx_hittest *bc = (bezctx_hittest *)z;
+ double r_min = bc->r_min;
+
+ if (p_knot_idx)
+ *p_knot_idx = bc->knot_idx_min;
+
+ zfree(z);
+ return r_min;
+}
diff --git a/third_party/spiro/ppedit/bezctx_hittest.h b/third_party/spiro/ppedit/bezctx_hittest.h
new file mode 100644
index 0000000..79949ae
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx_hittest.h
@@ -0,0 +1,5 @@
+bezctx *
+new_bezctx_hittest(double x, double y);
+
+double
+bezctx_hittest_report(bezctx *z, int *p_knot_idx);
diff --git a/third_party/spiro/ppedit/bezctx_intf.h b/third_party/spiro/ppedit/bezctx_intf.h
new file mode 100644
index 0000000..a47b8ef
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx_intf.h
@@ -0,0 +1,20 @@
+typedef struct _bezctx bezctx;
+
+bezctx *
+new_bezctx(void);
+
+void
+bezctx_moveto(bezctx *bc, double x, double y, int is_open);
+
+void
+bezctx_lineto(bezctx *bc, double x, double y);
+
+void
+bezctx_quadto(bezctx *bc, double x1, double y1, double x2, double y2);
+
+void
+bezctx_curveto(bezctx *bc, double x1, double y1, double x2, double y2,
+ double x3, double y3);
+
+void
+bezctx_mark_knot(bezctx *bc, int knot_idx);
diff --git a/third_party/spiro/ppedit/bezctx_libart.c b/third_party/spiro/ppedit/bezctx_libart.c
new file mode 100644
index 0000000..c6b268f
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx_libart.c
@@ -0,0 +1,131 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include <libart_lgpl/libart.h>
+#include "zmisc.h"
+#include "bezctx.h"
+#include "bezctx_libart.h"
+
+typedef struct {
+ bezctx base;
+ int n_bez;
+ int n_bez_max;
+ ArtBpath *bez;
+} bezctx_libart;
+
+static void
+bezctx_libart_moveto(bezctx *z, double x, double y, int is_open) {
+ bezctx_libart *bc = (bezctx_libart *)z;
+ ArtBpath *bp;
+
+ if (bc->n_bez == bc->n_bez_max) {
+ bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1);
+ }
+ bp = &bc->bez[bc->n_bez++];
+ bp->code = is_open ? ART_MOVETO_OPEN : ART_MOVETO;
+ bp->x3 = x;
+ bp->y3 = y;
+}
+
+void
+bezctx_libart_lineto(bezctx *z, double x, double y) {
+ bezctx_libart *bc = (bezctx_libart *)z;
+ ArtBpath *bp;
+
+ if (bc->n_bez == bc->n_bez_max) {
+ bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1);
+ }
+ bp = &bc->bez[bc->n_bez++];
+ bp->code = ART_LINETO;
+ bp->x3 = x;
+ bp->y3 = y;
+}
+
+void
+bezctx_libart_quadto(bezctx *z, double x1, double y1, double x2, double y2)
+{
+ bezctx_libart *bc = (bezctx_libart *)z;
+ ArtBpath *bp;
+ double x0, y0;
+
+ if (bc->n_bez == bc->n_bez_max) {
+ bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1);
+ }
+ bp = &bc->bez[bc->n_bez++];
+ x0 = bp[-1].x3;
+ y0 = bp[-1].y3;
+ bp->code = ART_CURVETO;
+ bp->x1 = x1 + (1./3) * (x0 - x1);
+ bp->y1 = y1 + (1./3) * (y0 - y1);
+ bp->x2 = x1 + (1./3) * (x2 - x1);
+ bp->y2 = y1 + (1./3) * (y2 - y1);
+ bp->x3 = x2;
+ bp->y3 = y2;
+}
+
+void
+bezctx_libart_curveto(bezctx *z, double x1, double y1, double x2, double y2,
+ double x3, double y3)
+{
+ bezctx_libart *bc = (bezctx_libart *)z;
+ ArtBpath *bp;
+
+ if (bc->n_bez == bc->n_bez_max) {
+ bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1);
+ }
+ bp = &bc->bez[bc->n_bez++];
+ bp->code = ART_CURVETO;
+ bp->x1 = x1;
+ bp->y1 = y1;
+ bp->x2 = x2;
+ bp->y2 = y2;
+ bp->x3 = x3;
+ bp->y3 = y3;
+}
+
+ArtBpath *
+bezctx_to_bpath(bezctx *z) {
+ bezctx_libart *bc = (bezctx_libart *)z;
+ ArtBpath *result;
+
+ if (bc->n_bez == bc->n_bez_max) {
+ bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1);
+ }
+ bc->bez[bc->n_bez].code = ART_END;
+ result = bc->bez;
+ zfree(bc);
+ return result;
+}
+
+bezctx *
+new_bezctx_libart(void) {
+ bezctx_libart *result = znew(bezctx_libart, 1);
+
+ result->base.moveto = bezctx_libart_moveto;
+ result->base.lineto = bezctx_libart_lineto;
+ result->base.quadto = bezctx_libart_quadto;
+ result->base.curveto = bezctx_libart_curveto;
+ result->base.mark_knot = NULL;
+ result->n_bez = 0;
+ result->n_bez_max = 4;
+ result->bez = znew(ArtBpath, result->n_bez_max);
+ return &result->base;
+}
+
diff --git a/third_party/spiro/ppedit/bezctx_libart.h b/third_party/spiro/ppedit/bezctx_libart.h
new file mode 100644
index 0000000..c831b98
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx_libart.h
@@ -0,0 +1,5 @@
+
+bezctx *new_bezctx_libart(void);
+
+ArtBpath *
+bezctx_to_bpath(bezctx *bc);
diff --git a/third_party/spiro/ppedit/bezctx_ps.c b/third_party/spiro/ppedit/bezctx_ps.c
new file mode 100644
index 0000000..05a38ae
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx_ps.c
@@ -0,0 +1,116 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include <stdio.h>
+
+#include "zmisc.h"
+#include "bezctx.h"
+#include "bezctx_ps.h"
+
+typedef struct {
+ bezctx base;
+ int is_open;
+ double x, y;
+ FILE *f;
+} bezctx_ps;
+
+const char *ps_prolog = "%!PS\n"
+"/m { moveto } bind def\n"
+"/l { lineto } bind def\n"
+"/c { curveto } bind def\n"
+"/z { closepath } bind def\n"
+"1 -1 scale\n"
+"0 -792 translate\n";
+
+const char *ps_postlog = "stroke\n"
+"showpage\n";
+
+static void
+bezctx_ps_moveto(bezctx *z, double x, double y, int is_open) {
+ bezctx_ps *bc = (bezctx_ps *)z;
+
+ if (!bc->is_open) fprintf(bc->f, "z\n");
+ fprintf(bc->f, "%g %g m\n", x, y);
+ bc->is_open = is_open;
+ bc->x = x;
+ bc->y = y;
+}
+
+void
+bezctx_ps_lineto(bezctx *z, double x, double y) {
+ bezctx_ps *bc = (bezctx_ps *)z;
+
+ fprintf(bc->f, "%g %g l\n", x, y);
+ bc->x = x;
+ bc->y = y;
+}
+
+void
+bezctx_ps_quadto(bezctx *z, double xm, double ym, double x3, double y3)
+{
+ bezctx_ps *bc = (bezctx_ps *)z;
+ double x0, y0;
+ double x1, y1;
+ double x2, y2;
+
+ x0 = bc->x;
+ y0 = bc->y;
+ x1 = xm + (1./3) * (x0 - xm);
+ y1 = ym + (1./3) * (y0 - ym);
+ x2 = xm + (1./3) * (x3 - xm);
+ y2 = ym + (1./3) * (y3 - ym);
+ fprintf(bc->f, "%g %g %g %g %g %g c\n", x1, y1, x2, y2, x3, y3);
+ bc->x = x3;
+ bc->y = y3;
+}
+
+void
+bezctx_ps_curveto(bezctx *z, double x1, double y1, double x2, double y2,
+ double x3, double y3)
+{
+ bezctx_ps *bc = (bezctx_ps *)z;
+
+ fprintf(bc->f, "%g %g %g %g %g %g c\n", x1, y1, x2, y2, x3, y3);
+ bc->x = x3;
+ bc->y = y3;
+}
+
+bezctx *
+new_bezctx_ps(FILE *f) {
+ bezctx_ps *result = znew(bezctx_ps, 1);
+
+ result->base.moveto = bezctx_ps_moveto;
+ result->base.lineto = bezctx_ps_lineto;
+ result->base.quadto = bezctx_ps_quadto;
+ result->base.curveto = bezctx_ps_curveto;
+ result->base.mark_knot = NULL;
+ result->is_open = 1;
+ result->f = f;
+ return &result->base;
+}
+
+void
+bezctx_ps_close(bezctx *z)
+{
+ bezctx_ps *bc = (bezctx_ps *)z;
+
+ if (!bc->is_open) fprintf(bc->f, "z\n");
+ zfree(bc);
+}
diff --git a/third_party/spiro/ppedit/bezctx_ps.h b/third_party/spiro/ppedit/bezctx_ps.h
new file mode 100644
index 0000000..5190fd7
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx_ps.h
@@ -0,0 +1,8 @@
+
+const char *ps_prolog;
+const char *ps_postlog;
+
+bezctx *new_bezctx_ps(FILE *f);
+
+void
+bezctx_ps_close(bezctx *bc);
diff --git a/third_party/spiro/ppedit/bezctx_quartz.c b/third_party/spiro/ppedit/bezctx_quartz.c
new file mode 100644
index 0000000..f04e0bc
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx_quartz.c
@@ -0,0 +1,78 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include <Carbon/Carbon.h>
+
+#include "zmisc.h"
+#include "bezctx.h"
+#include "bezctx_quartz.h"
+
+
+typedef struct {
+ bezctx base;
+ CGMutablePathRef pathref;
+ int is_open;
+} bezctx_quartz;
+
+static void
+bezctx_quartz_moveto(bezctx *z, double x, double y, int is_open) {
+ bezctx_quartz *bc = (bezctx_quartz *)z;
+ if (!bc->is_open) CGPathCloseSubpath(bc->pathref);
+ CGPathMoveToPoint(bc->pathref, NULL, x, y);
+ bc->is_open = is_open;
+}
+
+static void
+bezctx_quartz_lineto(bezctx *z, double x, double y) {
+ bezctx_quartz *bc = (bezctx_quartz *)z;
+ CGPathAddLineToPoint(bc->pathref, NULL, x, y);
+}
+
+static void
+bezctx_quartz_quadto(bezctx *z, double x1, double y1, double x2, double y2)
+{
+ bezctx_quartz *bc = (bezctx_quartz *)z;
+ CGPathAddQuadCurveToPoint(bc->pathref, NULL, x1, y1, x2, y2);
+}
+
+bezctx *
+new_bezctx_quartz(void) {
+ bezctx_quartz *result = znew(bezctx_quartz, 1);
+
+ result->base.moveto = bezctx_quartz_moveto;
+ result->base.lineto = bezctx_quartz_lineto;
+ result->base.quadto = bezctx_quartz_quadto;
+ result->base.mark_knot = NULL;
+ result->pathref = CGPathCreateMutable();
+ result->is_open = 1;
+ return &result->base;
+}
+
+
+CGMutablePathRef
+bezctx_to_quartz(bezctx *z)
+{
+ bezctx_quartz *bc = (bezctx_quartz *)z;
+ CGMutablePathRef result = bc->pathref;
+
+ if (!bc->is_open) CGPathCloseSubpath(result);
+ zfree(bc);
+ return result;
+}
diff --git a/third_party/spiro/ppedit/bezctx_quartz.h b/third_party/spiro/ppedit/bezctx_quartz.h
new file mode 100644
index 0000000..e234c08
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx_quartz.h
@@ -0,0 +1,4 @@
+bezctx *new_bezctx_quartz(void);
+
+CGMutablePathRef
+bezctx_to_quartz(bezctx *bc);
diff --git a/third_party/spiro/ppedit/bezctx_x3.c b/third_party/spiro/ppedit/bezctx_x3.c
new file mode 100644
index 0000000..9278910
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx_x3.c
@@ -0,0 +1,96 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include <x3.h>
+#include "zmisc.h"
+#include "bezctx.h"
+#include "bezctx_x3.h"
+
+typedef struct {
+ bezctx base;
+ x3dc *dc;
+ int is_open;
+} bezctx_x3;
+
+static void
+bezctx_x3_moveto(bezctx *z, double x, double y, int is_open) {
+ bezctx_x3 *bc = (bezctx_x3 *)z;
+
+ if (!bc->is_open) x3closepath(bc->dc);
+ x3moveto(bc->dc, x, y);
+ bc->is_open = is_open;
+}
+
+void
+bezctx_x3_lineto(bezctx *z, double x, double y) {
+ bezctx_x3 *bc = (bezctx_x3 *)z;
+
+ x3lineto(bc->dc, x, y);
+}
+
+void
+bezctx_x3_quadto(bezctx *z, double x1, double y1, double x2, double y2)
+{
+ bezctx_x3 *bc = (bezctx_x3 *)z;
+ double x0, y0;
+
+ x3getcurrentpoint(bc->dc, &x0, &y0);
+ x3curveto(bc->dc,
+ x1 + (1./3) * (x0 - x1),
+ y1 + (1./3) * (y0 - y1),
+ x1 + (1./3) * (x2 - x1),
+ y1 + (1./3) * (y2 - y1),
+ x2,
+ y2);
+}
+
+void
+bezctx_x3_curveto(bezctx *z, double x1, double y1, double x2, double y2,
+ double x3, double y3)
+{
+ bezctx_x3 *bc = (bezctx_x3 *)z;
+
+ x3curveto(bc->dc, x1, y1, x2, y2, x3, y3);
+}
+
+void
+bezctx_x3_finish(bezctx *z)
+{
+ bezctx_x3 *bc = (bezctx_x3 *)z;
+
+ if (!bc->is_open)
+ x3closepath(bc->dc);
+
+ zfree(bc);
+}
+
+bezctx *
+new_bezctx_x3(x3dc *dc) {
+ bezctx_x3 *result = znew(bezctx_x3, 1);
+
+ result->base.moveto = bezctx_x3_moveto;
+ result->base.lineto = bezctx_x3_lineto;
+ result->base.quadto = bezctx_x3_quadto;
+ result->base.curveto = bezctx_x3_curveto;
+ result->base.mark_knot = NULL;
+ result->dc = dc;
+ result->is_open = 1;
+ return &result->base;
+}
diff --git a/third_party/spiro/ppedit/bezctx_x3.h b/third_party/spiro/ppedit/bezctx_x3.h
new file mode 100644
index 0000000..c7b1bd4
--- /dev/null
+++ b/third_party/spiro/ppedit/bezctx_x3.h
@@ -0,0 +1,3 @@
+
+bezctx *new_bezctx_x3(x3dc *dc);
+void bezctx_x3_finish(bezctx *z);
diff --git a/third_party/spiro/ppedit/carbon_main.c b/third_party/spiro/ppedit/carbon_main.c
new file mode 100644
index 0000000..5fbd83f
--- /dev/null
+++ b/third_party/spiro/ppedit/carbon_main.c
@@ -0,0 +1,182 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include <Carbon/Carbon.h>
+
+#include "bezctx.h"
+#include "bezctx_quartz.h"
+#include "plate.h"
+#include "pe_view.h"
+
+#define kCommandToggleCorner 'togC'
+
+typedef struct {
+ WindowRef window_ref;
+ plate *p;
+
+ HIViewRef view;
+} plate_edit;
+
+int n_iter = 10;
+
+pascal OSStatus my_handler(EventHandlerCallRef nextHandler, EventRef theEvent, void *data)
+{
+ plate_edit *pe = (plate_edit *)data;
+ WindowRef window = pe->window_ref;
+ UInt32 klass = GetEventClass(theEvent);
+ UInt32 kind = GetEventKind(theEvent);
+ OSStatus result = eventNotHandledErr;
+ OSStatus err;
+ Point where;
+ Rect bounds;
+
+ switch (klass) {
+ case kEventClassMouse:
+ switch (kind) {
+ case kEventMouseDown:
+ err = GetEventParameter(theEvent, kEventParamMouseLocation,
+ typeQDPoint, NULL, sizeof(where), NULL, &where);
+ printf("mouse down %d %d\n", where.h, where.v);
+ break;
+ }
+ break;
+ case kEventClassWindow:
+ switch (kind) {
+ case kEventWindowDrawContent:
+ printf("draw content\n");
+ result = noErr;
+ break;
+ case kEventWindowClickContentRgn:
+ printf("click_content region\n");
+ break;
+ case kEventWindowHandleContentClick:
+ err = GetEventParameter(theEvent, kEventParamMouseLocation,
+ typeQDPoint, NULL, sizeof(where), NULL, &where);
+ GetWindowBounds(window, kWindowContentRgn, &bounds);
+ printf("content click %d, %d; %d, %d\n", where.h, where.v,
+ where.h - bounds.left, where.v - bounds.top);
+ break;
+ }
+ }
+ return result;
+}
+
+pascal OSStatus app_handler(EventHandlerCallRef nextHandler, EventRef theEvent, void *data)
+{
+ plate_edit *pe = (plate_edit *)data;
+ HICommand hiCommand;
+ OSStatus result = eventNotHandledErr;
+ GetEventParameter(theEvent, kEventParamDirectObject, typeHICommand,NULL,
+ sizeof(HICommand),NULL,&hiCommand);
+ unsigned int c = hiCommand.commandID;
+ MenuRef menu;
+ MenuItemIndex ix;
+
+ printf("app_handler %c%c%c%c\n",
+ (c >> 24) & 255, (c >> 16) & 255, (c >> 8) & 255, c & 255);
+ switch (c) {
+ case kHICommandUndo:
+ GetIndMenuItemWithCommandID(NULL, kHICommandUndo, 1, &menu, &ix);
+ SetMenuItemTextWithCFString(menu, ix, CFSTR("Undo disabled"));
+ DisableMenuItem(menu, ix);
+ break;
+ case kCommandToggleCorner:
+ pe_view_toggle_corner(pe->view);
+ break;
+ }
+ return result;
+}
+
+void
+add_pe_view(WindowRef window, plate_edit *pe, plate *p)
+{
+ HIRect rect = {{0, 0}, {32767, 32767}};
+ HIViewRef view;
+
+ pe_view_create(window, &rect, &view);
+ pe_view_set_plate(view, p);
+ HIViewSetVisible(view, true);
+ pe->view = view;
+}
+
+void
+init_window(WindowRef window, plate_edit *pe)
+{
+ EventTypeSpec app_event_types[] = {
+ { kEventClassCommand, kEventProcessCommand }
+ };
+ EventTypeSpec event_types[] = {
+ { kEventClassWindow, kEventWindowDrawContent },
+ { kEventClassWindow, kEventWindowHandleContentClick },
+ { kEventClassMouse, kEventMouseDown }
+ };
+
+ InstallApplicationEventHandler(NewEventHandlerUPP(app_handler),
+ GetEventTypeCount(app_event_types),
+ app_event_types, (void *)pe, NULL);
+ InstallWindowEventHandler(window, NewEventHandlerUPP(my_handler),
+ GetEventTypeCount(event_types),
+ event_types, (void *)pe, NULL);
+
+ add_pe_view(window, pe, pe->p);
+}
+
+int main(int argc, char* argv[])
+{
+ IBNibRef nibRef;
+ WindowRef window;
+ plate_edit pe;
+
+ OSStatus err;
+
+ // Create a Nib reference passing the name of the nib file (without the .nib extension)
+ // CreateNibReference only searches into the application bundle.
+ err = CreateNibReference(CFSTR("main"), &nibRef);
+ require_noerr( err, CantGetNibRef );
+
+ // Once the nib reference is created, set the menu bar. "MainMenu" is the name of the menu bar
+ // object. This name is set in InterfaceBuilder when the nib is created.
+ err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"));
+ require_noerr( err, CantSetMenuBar );
+
+ // Then create a window. "MainWindow" is the name of the window object. This name is set in
+ // InterfaceBuilder when the nib is created.
+ err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &window);
+ require_noerr( err, CantCreateWindow );
+
+ // We don't need the nib reference anymore.
+ DisposeNibReference(nibRef);
+
+ pe.window_ref = window;
+ pe.p = file_read_plate("/Users/raph/golf/ppedit/g.plate");
+ if (pe.p == NULL)
+ pe.p = new_plate();
+ init_window(window, &pe);
+ // The window was created hidden so show it.
+ ShowWindow( window );
+
+ // Call the event loop
+ RunApplicationEventLoop();
+
+CantCreateWindow:
+CantSetMenuBar:
+CantGetNibRef:
+ return err;
+}
diff --git a/third_party/spiro/ppedit/cornu.c b/third_party/spiro/ppedit/cornu.c
new file mode 100644
index 0000000..b0eac68
--- /dev/null
+++ b/third_party/spiro/ppedit/cornu.c
@@ -0,0 +1,615 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "bezctx_intf.h"
+
+/* The computation of fresnel integrals is adapted from: */
+
+/*
+Cephes Math Library Release 2.1: December, 1988
+Copyright 1984, 1987, 1988 by Stephen L. Moshier
+Direct inquiries to 30 Frost Street, Cambridge, MA 02140
+*/
+
+double polevl( double x, double coef[], int N )
+{
+double ans;
+int i;
+double *p;
+
+p = coef;
+ans = *p++;
+i = N;
+
+do
+ ans = ans * x + *p++;
+while( --i );
+
+return( ans );
+}
+
+/* p1evl() */
+/* N
+ * Evaluate polynomial when coefficient of x is 1.0.
+ * Otherwise same as polevl.
+ */
+
+double p1evl( double x, double coef[], int N )
+{
+double ans;
+double *p;
+int i;
+
+p = coef;
+ans = x + *p++;
+i = N-1;
+
+do
+ ans = ans * x + *p++;
+while( --i );
+
+return( ans );
+}
+
+static double sn[6] = {
+-2.99181919401019853726E3,
+ 7.08840045257738576863E5,
+-6.29741486205862506537E7,
+ 2.54890880573376359104E9,
+-4.42979518059697779103E10,
+ 3.18016297876567817986E11,
+};
+static double sd[6] = {
+/* 1.00000000000000000000E0,*/
+ 2.81376268889994315696E2,
+ 4.55847810806532581675E4,
+ 5.17343888770096400730E6,
+ 4.19320245898111231129E8,
+ 2.24411795645340920940E10,
+ 6.07366389490084639049E11,
+};
+
+static double cn[6] = {
+-4.98843114573573548651E-8,
+ 9.50428062829859605134E-6,
+-6.45191435683965050962E-4,
+ 1.88843319396703850064E-2,
+-2.05525900955013891793E-1,
+ 9.99999999999999998822E-1,
+};
+static double cd[7] = {
+ 3.99982968972495980367E-12,
+ 9.15439215774657478799E-10,
+ 1.25001862479598821474E-7,
+ 1.22262789024179030997E-5,
+ 8.68029542941784300606E-4,
+ 4.12142090722199792936E-2,
+ 1.00000000000000000118E0,
+};
+static double fn[10] = {
+ 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,
+};
+static double fd[10] = {
+/* 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,
+};
+static double gn[11] = {
+ 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,
+};
+static double gd[11] = {
+/* 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,
+};
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846 /* pi */
+#endif
+#ifndef M_PI_2
+#define M_PI_2 1.57079632679489661923 /* pi/2 */
+#endif
+
+int fresnl( xxa, ssa, cca )
+double xxa, *ssa, *cca;
+{
+double f, g, cc, ss, c, s, t, u;
+double x, x2;
+
+x = fabs(xxa);
+x2 = x * x;
+if( x2 < 2.5625 )
+ {
+ t = x2 * x2;
+ ss = x * x2 * polevl( t, sn, 5)/p1evl( t, sd, 6 );
+ cc = x * polevl( t, cn, 5)/polevl(t, cd, 6 );
+ goto done;
+ }
+
+
+
+
+
+#if 0
+/* Note by RLL: the cutoff here seems low to me; perhaps it should be
+ eliminated altogether. */
+if( x > 36974.0 )
+ {
+ cc = 0.5;
+ ss = 0.5;
+ goto done;
+ }
+#endif
+
+/* Asymptotic power series auxiliary functions
+ * for large argument
+ */
+ x2 = x * x;
+ t = M_PI * x2;
+ u = 1.0/(t * t);
+ t = 1.0/t;
+ f = 1.0 - u * polevl( u, fn, 9)/p1evl(u, fd, 10);
+ g = t * polevl( u, gn, 10)/p1evl(u, gd, 11);
+
+ t = M_PI_2 * x2;
+ c = cos(t);
+ s = sin(t);
+ t = M_PI * x;
+ cc = 0.5 + (f * s - g * c)/t;
+ ss = 0.5 - (f * c + g * s)/t;
+
+done:
+if( xxa < 0.0 )
+ {
+ cc = -cc;
+ ss = -ss;
+ }
+
+*cca = cc;
+*ssa = ss;
+return(0);
+}
+
+/*
+ End section adapted from Cephes math library. The following code is
+ by Raph Levien.
+*/
+
+void eval_cornu(double t, double *ps, double *pc)
+{
+ double s, c;
+ double rspio2 = 0.7978845608028653; /* 1 / sqrt(pi/2) */
+ double spio2 = 1.2533141373155; /* sqrt(pi/2) */
+
+ fresnl(t * rspio2, &s, &c);
+ *ps = s * spio2;
+ *pc = c * spio2;
+}
+
+double mod_2pi(double th) {
+ double u = th * (1 / (2 * M_PI));
+ return 2 * M_PI * (u - floor(u + 0.5));
+}
+
+void fit_cornu_half(double th0, double th1,
+ double *pt0, double *pt1,
+ double *pk0, double *pk1)
+{
+ int i;
+ const int max_iter = 21;
+ double t0, t1, t_m;
+ double tl, tr, t_est;
+ double s0, c0, s1, c1;
+ /* This implementation uses bisection, which is simple but almost
+ certainly not the fastest converging. If time is of the essence,
+ use something like Newton-Raphson. */
+
+ if (fabs(th0 + th1) < 1e-6) {
+ th0 += 1e-6;
+ th1 += 1e-6;
+ }
+ t_est = 0.29112 * (th1 + th0) / sqrt(th1 - th0);
+ tl = t_est * .9;
+ tr = t_est * 2;
+ for (i = 0; i < max_iter; i++) {
+ double dt;
+ double chord_th;
+
+ t_m = .5 * (tl + tr);
+ dt = (th0 + th1) / (4 * t_m);
+ t0 = t_m - dt;
+ t1 = t_m + dt;
+ eval_cornu(t0, &s0, &c0);
+ eval_cornu(t1, &s1, &c1);
+ chord_th = atan2(s1 - s0, c1 - c0);
+ if (mod_2pi(chord_th - t0 * t0 - th0) < 0)
+ tl = t_m;
+ else
+ tr = t_m;
+ }
+ *pt0 = t0;
+ *pt1 = t1;
+ if (pk0 || pk1) {
+ double chordlen = hypot(s1 - s0, c1 - c0);
+ if (pk0) *pk0 = t0 * chordlen;
+ if (pk1) *pk1 = t1 * chordlen;
+ }
+}
+
+/* Most of the time, this should give a fairly tight, yet conservative,
+ (meaning it won't underestimate) approximation of the maximum error
+ between a Cornu spiral segment and its quadratic Bezier fit.
+*/
+double
+est_cornu_error(double t0, double t1)
+{
+ double t, u, est;
+
+ if (t0 < 0 || t1 < 0) {
+ t0 = -t0;
+ t1 = -t1;
+ }
+ if (t1 < 0) {
+ fprintf(stderr, "unexpected t1 sign\n");
+ }
+ if (t1 < t0) {
+ double tmp = t0;
+ t0 = t1;
+ t1 = tmp;
+ }
+ if (fabs(t0) < 1e-9) {
+ est = t1 * t1 * t1;
+ est *= .017256 - .0059 - est * t1;
+ } else {
+ t = t1 - t0;
+ t *= t;
+ t *= t;
+ est = t * fabs(t0 + t1 - 1.22084) / (t0 + t1);
+ u = t0 + t1 + .6;
+ u = u * u * u;
+ est *= .014 * (.6 * u + 1);
+ est += t * (t1 - t0) * .004;
+ }
+ return est;
+}
+
+void
+affine_pt(const double aff[6], double x, double y, double *px, double *py) {
+ *px = x * aff[0] + y * aff[2] + aff[4];
+ *py = x * aff[1] + y * aff[3] + aff[5];
+}
+
+void
+affine_multiply(double dst[6], const double src1[6], const double src2[6])
+{
+ double d0, d1, d2, d3, d4, d5;
+ d0 = src1[0] * src2[0] + src1[1] * src2[2];
+ d1 = src1[0] * src2[1] + src1[1] * src2[3];
+ d2 = src1[2] * src2[0] + src1[3] * src2[2];
+ d3 = src1[2] * src2[1] + src1[3] * src2[3];
+ d4 = src1[4] * src2[0] + src1[5] * src2[2] + src2[4];
+ d5 = src1[4] * src2[1] + src1[5] * src2[3] + src2[5];
+ dst[0] = d0;
+ dst[1] = d1;
+ dst[2] = d2;
+ dst[3] = d3;
+ dst[4] = d4;
+ dst[5] = d5;
+}
+
+void
+fit_quadratic(double x0, double y0, double th0,
+ double x1, double y1, double th1,
+ double quad[6]) {
+ double th;
+ double s0, c0, s1, c1;
+ double det, s, c;
+
+ th = atan2(y1 - y0, x1 - x0);
+ s0 = sin(th0 - th);
+ c0 = cos(th0 - th);
+ s1 = sin(th - th1);
+ c1 = cos(th - th1);
+ det = 1 / (s0 * c1 + s1 * c0);
+ s = s0 * s1 * det;
+ c = c0 * s1 * det;
+ quad[0] = x0;
+ quad[1] = y0;
+ quad[2] = x0 + (x1 - x0) * c - (y1 - y0) * s;
+ quad[3] = y0 + (y1 - y0) * c + (x1 - x0) * s;
+ quad[4] = x1;
+ quad[5] = y1;
+}
+
+void
+cornu_seg_to_quad(double t0, double t1, const double aff[6], bezctx *bc)
+{
+ double x0, y0;
+ double x1, y1;
+ double th0 = t0 * t0;
+ double th1 = t1 * t1;
+ double quad[6];
+ double qx1, qy1, qx2, qy2;
+
+ eval_cornu(t0, &y0, &x0);
+ eval_cornu(t1, &y1, &x1);
+ fit_quadratic(x0, y0, th0, x1, y1, th1, quad);
+ affine_pt(aff, quad[2], quad[3], &qx1, &qy1);
+ affine_pt(aff, quad[4], quad[5], &qx2, &qy2);
+ bezctx_quadto(bc, qx1, qy1, qx2, qy2);
+}
+
+void
+cornu_seg_to_bpath(double t0, double t1, const double aff[6],
+ bezctx *bc, double tol)
+{
+ double tm;
+
+ if ((t0 < 0 && t1 > 0) || (t1 < 0 && t0 > 0))
+ tm = 0;
+ else {
+ if (fabs(t0 * t0 - t1 * t1) < 1.5 &&
+ est_cornu_error(t0, t1) < tol) {
+ cornu_seg_to_quad(t0, t1, aff, bc);
+ return;
+ }
+#if 0
+ if (fabs(t0 - t1) < 1e-6) {
+ printf("DIVERGENCE!\007\n");
+ return;
+
+ }
+#endif
+ tm = (t0 + t1) * .5;
+ }
+
+ cornu_seg_to_bpath(t0, tm, aff, bc, tol);
+ cornu_seg_to_bpath(tm, t1, aff, bc, tol);
+}
+
+void
+cornu_to_bpath(const double xs[], const double ys[], const double ths[], int n,
+ bezctx *bc, double tol, int closed, int kt0, int n_kt)
+{
+ int i;
+
+ for (i = 0; i < n - 1 + closed; i++) {
+ double x0 = xs[i], y0 = ys[i];
+ int ip1 = (i + 1) % n;
+ double x1 = xs[ip1], y1 = ys[ip1];
+ double th = atan2(y1 - y0, x1 - x0);
+ double th0 = mod_2pi(ths[i] - th);
+ double th1 = mod_2pi(th - ths[ip1]);
+ double t0, t1;
+ double s0, c0, s1, c1;
+ double chord_th, chordlen, rot, scale;
+ double aff[6], aff2[6];
+ double flip = -1;
+
+ th1 += 1e-6;
+ if (th1 < th0) {
+ double tmp = th0;
+ th0 = th1;
+ th1 = tmp;
+ flip = 1;
+ }
+ fit_cornu_half(th0, th1, &t0, &t1, NULL, NULL);
+ if (flip == 1) {
+ double tmp = t0;
+ t0 = t1;
+ t1 = tmp;
+ }
+ eval_cornu(t0, &s0, &c0);
+ s0 *= flip;
+ eval_cornu(t1, &s1, &c1);
+ s1 *= flip;
+ chord_th = atan2(s1 - s0, c1 - c0);
+ chordlen = hypot(s1 - s0, c1 - c0);
+ rot = th - chord_th;
+ scale = hypot(y1 - y0, x1 - x0) / chordlen;
+ aff[0] = 1;
+ aff[1] = 0;
+ aff[2] = 0;
+ aff[3] = flip;
+ aff[4] = -c0;
+ aff[5] = -s0;
+ aff2[0] = scale * cos(rot);
+ aff2[1] = scale * sin(rot);
+ aff2[2] = -aff2[1];
+ aff2[3] = aff2[0];
+ aff2[4] = x0;
+ aff2[5] = y0;
+ affine_multiply(aff, aff, aff2);
+ bezctx_mark_knot(bc, (kt0 + i) % n_kt);
+ cornu_seg_to_bpath(t0, t1, aff, bc, tol / scale);
+ }
+}
+
+/* fit arc to pts (0, 0), (x, y), and (1, 0), return th tangent to
+ arc at (x, y) */
+double fit_arc(double x, double y) {
+ return atan2(y - 2 * x * y, y * y + x - x * x);
+}
+
+void
+local_ths(const double xs[], const double ys[], double ths[], int n,
+ int closed)
+{
+ int i;
+
+ for (i = 1 - closed; i < n - 1 + closed; i++) {
+ int im1 = (i + n - 1) % n;
+ double x0 = xs[im1];
+ double y0 = ys[im1];
+ double x1 = xs[i];
+ double y1 = ys[i];
+ int ip1 = (i + 1) % n;
+ double x2 = xs[ip1];
+ double y2 = ys[ip1];
+ double dx = x2 - x0;
+ double dy = y2 - y0;
+ double ir2 = dx * dx + dy * dy;
+ double x = ((x1 - x0) * dx + (y1 - y0) * dy) / ir2;
+ double y = ((y1 - y0) * dx - (x1 - x0) * dy) / ir2;
+ if (dx == 0.0 && dy == 0.0)
+ ths[i] = 0.0;
+ else
+ ths[i] = fit_arc(x, y) + atan2(dy, dx);
+ }
+}
+
+void
+endpoint_ths(const double xs[], const double ys[], double ths[], int n)
+{
+ ths[0] = 2 * atan2(ys[1] - ys[0], xs[1] - xs[0]) - ths[1];
+ ths[n - 1] = 2 * atan2(ys[n - 1] - ys[n - 2], xs[n - 1] - xs[n-2]) - ths[n - 2];
+}
+
+void
+tweak_ths(const double xs[], const double ys[], double ths[], int n,
+ double delt, int closed)
+{
+ double *dks = (double *)malloc(sizeof(double) * n);
+ int i;
+ double first_k0, last_k1;
+
+ for (i = 0; i < n - 1 + closed; i++) {
+ double x0 = xs[i];
+ double y0 = ys[i];
+ int ip1 = (i + 1) % n;
+ double x1 = xs[ip1];
+ double y1 = ys[ip1];
+ double th, th0, th1;
+ double t0, t1, k0, k1;
+ double s0, c0, s1, c1;
+ double scale;
+ double flip = -1;
+
+ if (x0 == x1 && y0 == y1) {
+#ifdef VERBOSE
+ printf("Overlapping points (i=%d)\n", i);
+#endif
+ /* Very naughty, casting off the constness like this. */
+ ((double*) xs)[i] = x1 = x1 + 1e-6;
+ }
+
+ th = atan2(y1 - y0, x1 - x0);
+ th0 = mod_2pi(ths[i] - th);
+ th1 = mod_2pi(th - ths[ip1]);
+
+ th1 += 1e-6;
+ if (th1 < th0) {
+ double tmp = th0;
+ th0 = th1;
+ th1 = tmp;
+ flip = 1;
+ }
+ fit_cornu_half(th0, th1, &t0, &t1, &k0, &k1);
+ if (flip == 1) {
+ double tmp = t0;
+ t0 = t1;
+ t1 = tmp;
+
+ tmp = k0;
+ k0 = k1;
+ k1 = tmp;
+ }
+ eval_cornu(t0, &s0, &c0);
+ eval_cornu(t1, &s1, &c1);
+ scale = 1 / hypot(y1 - y0, x1 - x0);
+ k0 *= scale;
+ k1 *= scale;
+ if (i > 0) dks[i] = k0 - last_k1;
+ else first_k0 = k0;
+ last_k1 = k1;
+ }
+ if (closed)
+ dks[0] = first_k0 - last_k1;
+ for (i = 1 - closed; i < n - 1 + closed; i++) {
+ int im1 = (i + n - 1) % n;
+ double x0 = xs[im1];
+ double y0 = ys[im1];
+ double x1 = xs[i];
+ double y1 = ys[i];
+ int ip1 = (i + 1) % n;
+ double x2 = xs[ip1];
+ double y2 = ys[ip1];
+ double chord1 = hypot(x1 - x0, y1 - y0);
+ double chord2 = hypot(x2 - x1, y2 - y1);
+ ths[i] -= delt * dks[i] * chord1 * chord2 / (chord1 + chord2);
+ }
+ free(dks);
+}
+
+void test_cornu(void)
+{
+#if 0
+ int i;
+ for (i = -10; i < 100; i++) {
+ double t = 36974 * 1.2533141373155 + i * .1;
+ double s, c;
+ eval_cornu(t, &s, &c);
+ printf("%g %g %g\n", t, s, c);
+ }
+#else
+ double t0, t1;
+ fit_cornu_half(0, 1, &t0, &t1, 0, 0);
+ printf("%g %g\n", t0, t1);
+#endif
+}
diff --git a/third_party/spiro/ppedit/cornu.h b/third_party/spiro/ppedit/cornu.h
new file mode 100644
index 0000000..f4a955a
--- /dev/null
+++ b/third_party/spiro/ppedit/cornu.h
@@ -0,0 +1,13 @@
+void
+cornu_to_bpath(const double xs[], const double ys[], const double ths[], int n,
+ bezctx *bc, double tol, int closed, int kt0, int n_kt);
+
+void
+local_ths(const double xs[], const double ys[], double ths[], int n, int closed);
+
+void
+endpoint_ths(const double xs[], const double ys[], double ths[], int n);
+
+void
+tweak_ths(const double xs[], const double ys[], double ths[], int n,
+ double delt, int closed);
diff --git a/third_party/spiro/ppedit/gpl.txt b/third_party/spiro/ppedit/gpl.txt
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/third_party/spiro/ppedit/gpl.txt
@@ -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.
diff --git a/third_party/spiro/ppedit/image.c b/third_party/spiro/ppedit/image.c
new file mode 100644
index 0000000..215d27d
--- /dev/null
+++ b/third_party/spiro/ppedit/image.c
@@ -0,0 +1,141 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include <stdio.h>
+#include <string.h>
+#include "zmisc.h"
+#include "image.h"
+
+/* An image loaded into memory. */
+struct _image {
+ unsigned char *buf;
+ int width;
+ int height;
+ int rowstride;
+};
+
+static image *
+load_ppm_file(FILE *f, char **reason)
+{
+ image *result;
+ char line[256];
+ int xs, ys;
+ int depth;
+ int n;
+
+ fseek(f, 0, SEEK_SET);
+ fgets(line, sizeof(line), f);
+ do {
+ fgets(line, sizeof(line), f);
+ } while (line[0] == '#');
+ n = sscanf(line, "%d %d", &xs, &ys);
+ if (n != 2) {
+ *reason = "Error reading ppmraw size line";
+ fclose(f);
+ return NULL;
+ }
+ do {
+ fgets(line, sizeof(line), f);
+ } while (line[0] == '#');
+ n = sscanf(line, "%d", &depth);
+ if (n != 1) {
+ *reason = "Error reading ppmraw depth line";
+ fclose(f);
+ return NULL;
+ }
+ result = znew(image, 1);
+ result->rowstride = 3 * xs;
+ result->buf = zalloc(ys * result->rowstride);
+ result->width = xs;
+ result->height = ys;
+ fread(result->buf, 1, ys * result->rowstride, f);
+ fclose(f);
+ return result;
+}
+
+image *
+load_image_file(const char *fn, char **reason)
+{
+ FILE *f = fopen(fn, "rb");
+ unsigned char buf[256];
+ int n;
+
+ if (f == NULL) {
+ *reason = "Error opening file";
+ return NULL;
+ }
+ n = fread(buf, 1, sizeof(buf), f);
+ if (n < 4) {
+ *reason = "Short file";
+ fclose(f);
+ return NULL;
+ }
+ if (buf[0] != 'P' || buf[1] != '6') {
+ *reason = "Unrecognized magic";
+ fclose(f);
+ return NULL;
+ }
+ return load_ppm_file(f, reason);
+}
+
+void
+free_image(image *im)
+{
+ zfree(im->buf);
+ zfree(im);
+}
+
+void
+render_image(image *im, const double affine[6],
+ unsigned char *buf, int rowstride, int x0, int y0, int x1, int y1)
+{
+ int y;
+ unsigned char *dest_line = buf;
+ int src_x0 = x0;
+
+ for (y = y0; y < y1; y++) {
+ int src_y = y;
+
+ if (src_y >= 0 && src_y < im->height) {
+ unsigned char *img_line = im->buf + src_y * im->rowstride;
+ int left_pad = -src_x0;
+ int img_run, img_off, right_pad;
+
+ if (left_pad > x1 - x0) left_pad = x1 - x0;
+ if (left_pad > 0) {
+ memset(dest_line, 255, 3 * left_pad);
+ } else left_pad = 0;
+ img_off = src_x0;
+ if (img_off < 0) img_off = 0;
+ img_run = x1 - x0 - left_pad;
+ if (img_run > im->width - img_off) img_run = im->width - img_off;
+ if (img_run > 0) {
+ memcpy(dest_line + 3 * left_pad, img_line + 3 * img_off, 3 * img_run);
+ } else img_run = 0;
+ right_pad = x1 - x0 - left_pad - img_run;
+ if (right_pad > 0) {
+ memset(dest_line + 3 * (left_pad + img_run), 255, 3 * right_pad);
+ }
+ } else {
+ memset(dest_line, 255, rowstride);
+ }
+ dest_line += rowstride;
+ }
+}
diff --git a/third_party/spiro/ppedit/image.h b/third_party/spiro/ppedit/image.h
new file mode 100644
index 0000000..a472405
--- /dev/null
+++ b/third_party/spiro/ppedit/image.h
@@ -0,0 +1,11 @@
+typedef struct _image image;
+
+image *
+load_image_file(const char *fn, char **reason);
+
+void
+free_image(image *im);
+
+void
+render_image(image *im, const double affine[6],
+ unsigned char *buf, int rowstride, int x0, int y0, int x1, int y1);
diff --git a/third_party/spiro/ppedit/pe_view.c b/third_party/spiro/ppedit/pe_view.c
new file mode 100644
index 0000000..9909680
--- /dev/null
+++ b/third_party/spiro/ppedit/pe_view.c
@@ -0,0 +1,548 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+/* This module implements a Carbon HIView object for a pattern plate
+ editor. */
+
+#include <Carbon/Carbon.h>
+
+#include "bezctx.h"
+#include "bezctx_quartz.h"
+#include "plate.h"
+#include "pe_view.h"
+
+#define kPEViewClassID CFSTR("com.levien.ppedit.PEView")
+#define kPEViewPrivate 'PE_v'
+
+typedef struct
+{
+ HIViewRef view;
+ int show_knots;
+ plate *p;
+} pe_view_data;
+
+static OSStatus
+pe_view_construct(EventRef inEvent)
+{
+ OSStatus err;
+ pe_view_data *data;
+
+ data = (pe_view_data *)malloc(sizeof(pe_view_data));
+ require_action(data != NULL, CantMalloc, err = memFullErr);
+ err = GetEventParameter(inEvent, kEventParamHIObjectInstance,
+ typeHIObjectRef, NULL, sizeof(HIObjectRef), NULL,
+ (HIObjectRef *)&data->view);
+ require_noerr(err, ParameterMissing);
+ err = SetEventParameter(inEvent, kEventParamHIObjectInstance,
+ typeVoidPtr, sizeof(pe_view_data *), &data);
+
+ data->p = NULL;
+ data->show_knots = 1;
+
+ ParameterMissing:
+ if (err != noErr)
+ free(data);
+
+ CantMalloc:
+ return err;
+}
+
+static OSStatus
+pe_view_destruct(EventRef inEvent, pe_view_data *inData)
+{
+ free(inData);
+ return noErr;
+}
+
+static OSStatus
+pe_view_initialize(EventHandlerCallRef inCallRef, EventRef inEvent,
+ pe_view_data *inData)
+{
+ OSStatus err;
+ HIRect bounds;
+
+ err = CallNextEventHandler(inCallRef, inEvent);
+ require_noerr(err, TroubleInSuperClass);
+
+ err = GetEventParameter(inEvent, 'Boun', typeHIRect,
+ NULL, sizeof(HIRect), NULL, &bounds);
+ require_noerr(err, ParameterMissing);
+
+ HIViewSetFrame(inData->view, &bounds);
+
+ ParameterMissing:
+ TroubleInSuperClass:
+ return err;
+}
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846 /* pi */
+#endif
+
+static void
+cgcontext_set_rgba(CGContextRef ctx, unsigned int rgba)
+{
+ const double norm = 1.0 / 255;
+ CGContextSetRGBFillColor(ctx,
+ ((rgba >> 24) & 0xff) * norm,
+ ((rgba >> 16) & 0xff) * norm,
+ ((rgba >> 8) & 0xff) * norm,
+ (rgba & 0xff) * norm);
+}
+
+static void
+draw_dot(CGContextRef ctx, double x, double y, double r,
+ unsigned int rgba)
+{
+ cgcontext_set_rgba(ctx, rgba);
+ CGMutablePathRef path = CGPathCreateMutable();
+ CGPathAddArc(path, NULL, x, y, r, 0, 2 * M_PI, false);
+ CGContextAddPath(ctx, path);
+ CGPathRelease(path);
+ CGContextFillPath(ctx);
+}
+
+static void
+draw_raw_rect(CGContextRef ctx, double x0, double y0, double x1, double y1,
+ unsigned int rgba)
+{
+ HIRect rect;
+
+ cgcontext_set_rgba(ctx, rgba);
+ rect.origin.x = x0;
+ rect.origin.y = y0;
+ rect.size.width = x1 - x0;
+ rect.size.height = y1 - y0;
+ CGContextFillRect(ctx, rect);
+}
+
+static void
+draw_rect(CGContextRef ctx, double x, double y, double r,
+ unsigned int rgba)
+{
+ draw_raw_rect(ctx, x - r, y - r, x + r, y + r, rgba);
+}
+
+static void
+draw_plate(CGContextRef ctx, pe_view_data *pe)
+{
+ plate *p = pe->p;
+ int i, j;
+ bezctx *bc;
+ subpath *sp;
+ spiro_seg **ss = znew(spiro_seg *, p->n_sp);
+
+ for (i = 0; i < p->n_sp; i++) {
+ bc = new_bezctx_quartz();
+ ss[i] = draw_subpath(&p->sp[i], bc);
+ CGMutablePathRef path = bezctx_to_quartz(bc);
+ CGContextAddPath(ctx, path);
+ CGPathRelease(path);
+ CGContextStrokePath(ctx);
+ }
+
+ for (i = 0; i < p->n_sp; i++) {
+ if (pe->show_knots) {
+ sp = &p->sp[i];
+ for (j = 0; j < sp->n_kt; j++) {
+ knot *kt = &sp->kt[j];
+ kt_flags kf = kt->flags;
+ if ((kf & KT_SELECTED) && (kf & KT_OPEN)) {
+ draw_dot(ctx, kt->x, kt->y,
+ 3, 0x000000ff);
+ draw_dot(ctx, kt->x, kt->y,
+ 1.5, 0xffffffff);
+ } else if ((kf & KT_SELECTED) && (kf & KT_CORNER)) {
+ draw_rect(ctx, kt->x, kt->y,
+ 3, 0x000000ff);
+ draw_rect(ctx, kt->x, kt->y,
+ 1.5, 0xffffffff);
+ } else if (!(kf & KT_SELECTED) && (kf & KT_CORNER)) {
+ draw_rect(ctx, kt->x, kt->y,
+ 2.5, 0x000080ff);
+ } else {
+ draw_dot(ctx, kt->x, kt->y,
+ 2, 0x000080ff);
+ }
+ }
+ }
+ spiro_free(ss[i]);
+ }
+ zfree(ss);
+}
+
+static void
+draw_selection(CGContextRef ctx, pe_view_data *pe)
+{
+ plate *p = pe->p;
+
+ if (p->motmode == MOTION_MODE_SELECT) {
+ double rx0 = p->sel_x0;
+ double ry0 = p->sel_y0;
+ double rx1 = p->x0;
+ double ry1 = p->y0;
+ if (rx0 > rx1) {
+ double tmp = rx1;
+ rx1 = rx0;
+ rx0 = tmp;
+ }
+ if (ry0 > ry1) {
+ double tmp = ry1;
+ ry1 = ry0;
+ ry0 = tmp;
+ }
+ if (rx1 > rx0 && ry1 > ry0)
+ draw_raw_rect(ctx, rx0, ry0, rx1, ry1, 0x0000ff20);
+ }
+}
+
+static OSStatus
+pe_view_draw(EventRef inEvent, pe_view_data *inData)
+{
+ OSStatus err;
+ CGContextRef ctx;
+
+ err = GetEventParameter(inEvent, kEventParamCGContextRef, typeCGContextRef,
+ NULL, sizeof(CGContextRef), NULL, &ctx);
+ require_noerr(err, ParameterMissing);
+
+ if (inData->p) {
+ draw_plate(ctx, inData);
+ draw_selection(ctx, inData);
+ }
+
+ ParameterMissing:
+ return err;
+}
+
+static OSStatus
+pe_view_get_data(EventRef inEvent, pe_view_data *inData)
+{
+ OSStatus err;
+ OSType tag;
+ Ptr ptr;
+ Size outSize;
+
+ /* Probably could use a bit more error checking here, for type
+ and size match. Also, just returning a pe_view_data seems a
+ little hacky. */
+ err = GetEventParameter(inEvent, kEventParamControlDataTag, typeEnumeration,
+ NULL, sizeof(OSType), NULL, &tag);
+ require_noerr(err, ParameterMissing);
+
+ err = GetEventParameter(inEvent, kEventParamControlDataBuffer, typePtr,
+ NULL, sizeof(Ptr), NULL, &ptr);
+
+ if (tag == kPEViewPrivate) {
+ *((pe_view_data **)ptr) = inData;
+ outSize = sizeof(pe_view_data *);
+ } else
+ err = errDataNotSupported;
+
+ if (err == noErr)
+ err = SetEventParameter(inEvent, kEventParamControlDataBufferSize, typeLongInteger,
+ sizeof(Size), &outSize);
+
+ ParameterMissing:
+ return err;
+}
+
+static OSStatus
+pe_view_set_data(EventRef inEvent, pe_view_data *inData)
+{
+ OSStatus err;
+ Ptr ptr;
+ OSType tag;
+
+ err = GetEventParameter(inEvent, kEventParamControlDataTag, typeEnumeration,
+ NULL, sizeof(OSType), NULL, &tag);
+ require_noerr(err, ParameterMissing);
+
+ err = GetEventParameter(inEvent, kEventParamControlDataBuffer, typePtr,
+ NULL, sizeof(Ptr), NULL, &ptr);
+ require_noerr(err, ParameterMissing);
+
+ if (tag == 'Plat') {
+ inData->p = *(plate **)ptr;
+ } else
+ err = errDataNotSupported;
+
+ ParameterMissing:
+ return err;
+}
+
+static OSStatus
+pe_view_hittest(EventRef inEvent, pe_view_data *inData)
+{
+ OSStatus err;
+ HIPoint where;
+ HIRect bounds;
+ ControlPartCode part;
+
+ err = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint,
+ NULL, sizeof(HIPoint), NULL, &where);
+ require_noerr(err, ParameterMissing);
+
+ err = HIViewGetBounds(inData->view, &bounds);
+ require_noerr(err, ParameterMissing);
+
+ if (CGRectContainsPoint(bounds, where))
+ part = 1;
+ else
+ part = kControlNoPart;
+ err = SetEventParameter(inEvent, kEventParamControlPart,
+ typeControlPartCode, sizeof(ControlPartCode),
+ &part);
+ printf("hittest %g, %g!\n", where.x, where.y);
+
+ ParameterMissing:
+ return err;
+}
+
+static void
+pe_view_queue_draw(pe_view_data *pe)
+{
+ HIViewSetNeedsDisplay(pe->view, true);
+}
+
+static int
+pe_view_button_press(pe_view_data *pe, double x, double y, press_mod mods)
+{
+ pe->p->description = NULL;
+ plate_press(pe->p, x, y, mods);
+ pe_view_queue_draw(pe);
+ return 1;
+}
+
+static int
+pe_view_motion(pe_view_data *pe, double x, double y)
+{
+ if (pe->p->motmode == MOTION_MODE_MOVE)
+ plate_motion_move(pe->p, x, y);
+ else if (pe->p->motmode == MOTION_MODE_SELECT)
+ plate_motion_select(pe->p, x, y);
+ pe_view_queue_draw(pe);
+ return 1;
+}
+
+static int
+pe_view_button_release(pe_view_data *pe)
+{
+ int need_redraw;
+
+ need_redraw = (pe->p->motmode == MOTION_MODE_SELECT);
+
+ plate_unpress(pe->p);
+
+ if (need_redraw)
+ pe_view_queue_draw(pe);
+ return 1;
+}
+
+static OSStatus
+pe_view_track(EventRef inEvent, pe_view_data *inData)
+{
+ OSStatus err;
+ HIPoint where;
+ MouseTrackingResult mouseStatus;
+ HIRect bounds;
+ Rect windBounds;
+ Point theQDPoint;
+
+ err = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint,
+ NULL, sizeof(HIPoint), NULL, &where);
+ require_noerr(err, ParameterMissing);
+
+ err = HIViewGetBounds(inData->view, &bounds);
+ require_noerr(err, ParameterMissing);
+
+ GetWindowBounds(GetControlOwner(inData->view), kWindowStructureRgn, &windBounds);
+
+#ifdef VERBOSE
+ printf("press: %g, %g!\n", where.x, where.y);
+#endif
+ pe_view_button_press(inData, where.x, where.y, 0);
+
+ mouseStatus = kMouseTrackingMouseDown;
+ while (mouseStatus != kMouseTrackingMouseUp) {
+ TrackMouseLocation(NULL, &theQDPoint, &mouseStatus);
+ where.x = theQDPoint.h - windBounds.left;
+ where.y = theQDPoint.v - windBounds.top;
+ HIViewConvertPoint(&where, NULL, inData->view);
+#ifdef VERBOSE
+ printf("track %d: %g, %g!\n", mouseStatus, where.x, where.y);
+#endif
+ if (mouseStatus == kMouseTrackingMouseUp) {
+ pe_view_button_release(inData);
+ } else {
+ pe_view_motion(inData, where.x, where.y);
+ }
+ }
+
+ ParameterMissing:
+ return err;
+}
+
+pascal OSStatus
+pe_view_handler(EventHandlerCallRef inCallRef,
+ EventRef inEvent,
+ void* inUserData )
+{
+ OSStatus err = eventNotHandledErr;
+ UInt32 eventClass = GetEventClass(inEvent);
+ UInt32 eventKind = GetEventKind(inEvent);
+ pe_view_data *data = (pe_view_data *)inUserData;
+
+ switch (eventClass) {
+ case kEventClassHIObject:
+ switch (eventKind) {
+ case kEventHIObjectConstruct:
+ err = pe_view_construct(inEvent);
+ break;
+ case kEventHIObjectInitialize:
+ err = pe_view_initialize(inCallRef, inEvent, data);
+ break;
+ case kEventHIObjectDestruct:
+ err = pe_view_destruct(inEvent, data);
+ break;
+ }
+ break;
+ case kEventClassControl:
+ switch (eventKind) {
+ case kEventControlInitialize:
+ err = noErr;
+ break;
+ case kEventControlDraw:
+ err = pe_view_draw(inEvent, data);
+ break;
+ case kEventControlGetData:
+ err = pe_view_get_data(inEvent, data);
+ break;
+ case kEventControlSetData:
+ err = pe_view_set_data(inEvent, data);
+ break;
+ case kEventControlTrack:
+ err = pe_view_track(inEvent, data);
+ break;
+ case kEventControlHitTest:
+ err = pe_view_hittest(inEvent, data);
+ break;
+ /*...*/
+ }
+ break;
+ }
+ return err;
+}
+
+static OSStatus
+pe_view_register(void)
+{
+ OSStatus err = noErr;
+ static HIObjectClassRef pe_view_ClassRef = NULL;
+
+ if (pe_view_ClassRef == NULL) {
+ EventTypeSpec eventList[] = {
+ { kEventClassHIObject, kEventHIObjectConstruct },
+ { kEventClassHIObject, kEventHIObjectInitialize },
+ { kEventClassHIObject, kEventHIObjectDestruct },
+
+ { kEventClassControl, kEventControlActivate },
+ { kEventClassControl, kEventControlDeactivate },
+ { kEventClassControl, kEventControlDraw },
+ { kEventClassControl, kEventControlHiliteChanged },
+ { kEventClassControl, kEventControlHitTest },
+ { kEventClassControl, kEventControlInitialize },
+ { kEventClassControl, kEventControlGetData },
+ { kEventClassControl, kEventControlSetData },
+ { kEventClassControl, kEventControlTrack }
+ };
+ err = HIObjectRegisterSubclass(kPEViewClassID,
+ kHIViewClassID,
+ NULL,
+ pe_view_handler,
+ GetEventTypeCount(eventList),
+ eventList,
+ NULL,
+ &pe_view_ClassRef);
+ }
+ return err;
+}
+
+OSStatus pe_view_create(
+ WindowRef inWindow,
+ const HIRect* inBounds,
+ HIViewRef* outView)
+{
+ OSStatus err;
+ EventRef event;
+
+ err = pe_view_register();
+ require_noerr(err, CantRegister);
+
+ err = CreateEvent(NULL, kEventClassHIObject, kEventHIObjectInitialize,
+ GetCurrentEventTime(), 0, &event);
+ require_noerr(err, CantCreateEvent);
+
+ if (inBounds != NULL) {
+ err = SetEventParameter(event, 'Boun', typeHIRect, sizeof(HIRect),
+ inBounds);
+ require_noerr(err, CantSetParameter);
+ }
+
+ err = HIObjectCreate(kPEViewClassID, event, (HIObjectRef*)outView);
+ require_noerr(err, CantCreate);
+
+ if (inWindow != NULL) {
+ HIViewRef root;
+ err = GetRootControl(inWindow, &root);
+ require_noerr(err, CantGetRootView);
+ err = HIViewAddSubview(root, *outView);
+ }
+ CantCreate:
+ CantGetRootView:
+ CantSetParameter:
+ CantCreateEvent:
+ ReleaseEvent(event);
+ CantRegister:
+ return err;
+}
+
+void
+pe_view_set_plate(HIViewRef view, plate *p)
+{
+ OSStatus err;
+
+ err = SetControlData(view, 1, 'Plat', 4, &p);
+}
+
+void
+pe_view_toggle_corner(HIViewRef view)
+{
+ OSStatus err;
+ pe_view_data *pe;
+
+ err = GetControlData(view, 1, kPEViewPrivate, 4, &pe, NULL);
+ require_noerr(err, CantGetPrivate);
+
+ plate_toggle_corner(pe->p);
+ pe_view_queue_draw(pe);
+
+ CantGetPrivate:
+}
diff --git a/third_party/spiro/ppedit/pe_view.h b/third_party/spiro/ppedit/pe_view.h
new file mode 100644
index 0000000..8db4fff
--- /dev/null
+++ b/third_party/spiro/ppedit/pe_view.h
@@ -0,0 +1,11 @@
+OSStatus pe_view_create(
+ WindowRef inWindow,
+ const HIRect* inBounds,
+ HIViewRef* outView);
+
+void
+pe_view_set_plate(HIViewRef view, plate *p);
+
+void
+pe_view_toggle_corner(HIViewRef view);
+
diff --git a/third_party/spiro/ppedit/plate.c b/third_party/spiro/ppedit/plate.c
new file mode 100644
index 0000000..023f95f
--- /dev/null
+++ b/third_party/spiro/ppedit/plate.c
@@ -0,0 +1,526 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include <string.h>
+#include <math.h>
+#include <stdio.h>
+
+#include "zmisc.h"
+#include "sexp.h"
+#include "bezctx_intf.h"
+#include "bezctx_hittest.h"
+#include "cornu.h"
+#include "spiro.h"
+#include "plate.h"
+
+/* This is a global while we're playing with the tangent solver. Once we get that
+ nailed down, it will go away. */
+extern int n_iter;
+
+/**
+ * These are functions for editing a Cornu spline ("plate"), intended
+ * to be somewhat independent of the UI toolkit specifics.
+ **/
+
+plate *
+new_plate(void)
+{
+ plate *p = znew(plate, 1);
+
+ p->n_sp = 0;
+ p->n_sp_max = 4;
+ p->sp = znew(subpath, p->n_sp_max);
+ p->mmode = MOUSE_MODE_ADD_CURVE;
+ p->last_curve_mmode = p->mmode;
+ return p;
+}
+
+void
+free_plate(plate *p)
+{
+ int i;
+ for (i = 0; i < p->n_sp; i++) {
+ subpath *sp = &p->sp[i];
+ zfree(sp->kt);
+ }
+ zfree(p->sp);
+ zfree(p);
+}
+
+plate *
+copy_plate(const plate *p)
+{
+ int i;
+ plate *n = znew(plate, 1);
+
+ n->n_sp = p->n_sp;
+ n->n_sp_max = p->n_sp_max;
+ n->sp = znew(subpath, n->n_sp_max);
+ for (i = 0; i < n->n_sp; i++) {
+ subpath *sp = &p->sp[i];
+ subpath *nsp = &n->sp[i];
+
+ nsp->n_kt = sp->n_kt;
+ nsp->n_kt_max = sp->n_kt_max;
+ nsp->kt = znew(knot, nsp->n_kt_max);
+ memcpy(nsp->kt, sp->kt, nsp->n_kt * sizeof(knot));
+ nsp->closed = sp->closed;
+ }
+ n->mmode = p->mmode;
+ return n;
+}
+
+void
+plate_select_all(plate *p, int selected)
+{
+ int i, j;
+
+ /* find an existing point to select, if any */
+ for (i = 0; i < p->n_sp; i++) {
+ subpath *sp = &p->sp[i];
+
+ for (j = 0; j < sp->n_kt; j++) {
+ knot *kt = &sp->kt[j];
+ kt->flags &= ~KT_SELECTED;
+ if (selected)
+ kt->flags |= KT_SELECTED;
+ }
+ }
+}
+
+subpath *
+plate_find_selected_sp(plate *p)
+{
+ int i, j;
+
+ /* find an existing point to select, if any */
+ for (i = 0; i < p->n_sp; i++) {
+ subpath *sp = &p->sp[i];
+
+ for (j = 0; j < sp->n_kt; j++) {
+ knot *kt = &sp->kt[j];
+ if (kt->flags & KT_SELECTED)
+ return sp;
+ }
+ }
+ return NULL;
+}
+
+subpath *
+plate_new_sp(plate *p)
+{
+ subpath *sp;
+ if (p->n_sp == p->n_sp_max)
+ p->sp = zrenew(subpath, p->sp, p->n_sp_max <<= 1);
+ sp = &p->sp[p->n_sp++];
+ sp->n_kt = 0;
+ sp->n_kt_max = 4;
+ sp->kt = znew(knot, sp->n_kt_max);
+ sp->closed = 0;
+ return sp;
+}
+
+static int
+try_close_sp(subpath *sp, int ix, int force)
+{
+ int n_kt = sp->n_kt;
+
+ if (sp->closed) return 0;
+ if (n_kt < 3) return 0;
+ if (!force) {
+ if (ix != 0 && ix != n_kt - 1) return 0;
+ if (!(sp->kt[n_kt - 1 - ix].flags & KT_SELECTED)) return 0;
+ }
+ sp->closed = 1;
+ return 1;
+}
+
+void
+plate_press(plate *p, double x, double y, press_mod mods)
+{
+ int i, j;
+ subpath *sp;
+ knot *kt;
+ const double srad = 5;
+ kt_flags new_kt_flags = KT_SELECTED;
+
+ if (p->mmode == MOUSE_MODE_ADD_CORNER)
+ new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_OPEN : KT_CORNER;
+ else if (p->mmode == MOUSE_MODE_ADD_CORNU)
+ new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_CORNU;
+ else if (p->mmode == MOUSE_MODE_ADD_LEFT)
+ new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_LEFT;
+ else if (p->mmode == MOUSE_MODE_ADD_RIGHT)
+ new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_RIGHT;
+ else
+ new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_OPEN;
+
+ p->x0 = x;
+ p->y0 = y;
+
+ /* find an existing point to select, if any */
+ for (i = 0; i < p->n_sp; i++) {
+ sp = &p->sp[i];
+
+ for (j = 0; j < sp->n_kt; j++) {
+ kt = &sp->kt[j];
+ if (hypot(kt->x - x, kt->y - y) < srad) {
+ int was_closed = try_close_sp(sp, j, mods & PRESS_MOD_DOUBLE);
+ if (mods & PRESS_MOD_SHIFT) {
+ kt->flags ^= KT_SELECTED;
+ } else if (!(kt->flags & KT_SELECTED)) {
+ plate_select_all(p, 0);
+ kt->flags |= KT_SELECTED;
+ }
+ p->description = was_closed ? "Close Path" : NULL;
+ p->motmode = MOTION_MODE_MOVE;
+ return;
+ }
+ }
+ }
+
+ if (p->mmode == MOUSE_MODE_ADD_RIGHT || p->mmode == MOUSE_MODE_ADD_LEFT)
+ p->mmode = p->last_curve_mmode;
+
+
+#if 1
+ /* test whether the button press was on a curve; if so, insert point */
+ for (i = 0; i < p->n_sp; i++) {
+ bezctx *bc = new_bezctx_hittest(x, y);
+ int knot_idx;
+
+ sp = &p->sp[i];
+ free_spiro(draw_subpath(sp, bc));
+ if (bezctx_hittest_report(bc, &knot_idx) < srad) {
+ knot *kt;
+
+ if (sp->n_kt == sp->n_kt_max)
+ sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1);
+ plate_select_all(p, 0);
+ kt = &sp->kt[knot_idx + 1];
+ memmove(&kt[1], kt, (sp->n_kt - knot_idx - 1) * sizeof(knot));
+ sp->n_kt++;
+ kt->x = x;
+ kt->y = y;
+ kt->flags = new_kt_flags;
+ p->description = "Insert Point";
+ p->motmode = MOTION_MODE_MOVE;
+ return;
+ }
+ }
+#endif
+
+ if (p->mmode == MOUSE_MODE_SELECT) {
+ plate_select_all(p, 0);
+ p->sel_x0 = x;
+ p->sel_y0 = y;
+ p->motmode = MOTION_MODE_SELECT;
+ return;
+ }
+
+ sp = plate_find_selected_sp(p);
+ if (sp == NULL || sp->closed) {
+ sp = plate_new_sp(p);
+ p->description = p->n_sp > 1 ? "New Subpath" : "New Path";
+ }
+
+ if (sp->n_kt == sp->n_kt_max)
+ sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1);
+ plate_select_all(p, 0);
+ kt = &sp->kt[sp->n_kt++];
+ kt->x = x;
+ kt->y = y;
+ kt->flags = new_kt_flags;
+ if (p->description == NULL)
+ p->description = "Add Point";
+ p->motmode = MOTION_MODE_MOVE;
+}
+
+void
+plate_motion_move(plate *p, double x, double y)
+{
+ int i, j, n = 0;
+ double dx, dy;
+
+ dx = x - p->x0;
+ dy = y - p->y0;
+ p->x0 = x;
+ p->y0 = y;
+
+ for (i = 0; i < p->n_sp; i++) {
+ subpath *sp = &p->sp[i];
+
+ for (j = 0; j < sp->n_kt; j++) {
+ knot *kt = &sp->kt[j];
+ if (kt->flags & KT_SELECTED) {
+ kt->x += dx;
+ kt->y += dy;
+ n++;
+ }
+ }
+ }
+ p->description = n == 1 ? "Move Point" : "Move Points";
+}
+
+void
+plate_motion_select(plate *p, double x1, double y1)
+{
+ int i, j;
+ double x0 = p->sel_x0;
+ double y0 = p->sel_y0;
+
+#ifdef VERBOSE
+ printf("plate_motion_select %g %g\n", x1, y1);
+#endif
+ p->x0 = x1;
+ p->y0 = y1;
+
+ if (x0 > x1) {
+ double tmp = x1;
+ x1 = x0;
+ x0 = tmp;
+ }
+ if (y0 > y1) {
+ double tmp = y1;
+ y1 = y0;
+ y0 = tmp;
+ }
+
+ for (i = 0; i < p->n_sp; i++) {
+ subpath *sp = &p->sp[i];
+
+ for (j = 0; j < sp->n_kt; j++) {
+ knot *kt = &sp->kt[j];
+ kt->flags &= ~KT_SELECTED;
+ if (kt->x >= x0 && kt->x <= x1 &&
+ kt->y >= y0 && kt->y <= y1)
+ kt->flags |= KT_SELECTED;
+ }
+ }
+}
+
+void plate_unpress(plate *p)
+{
+ p->motmode = MOTION_MODE_IDLE;
+}
+
+void
+plate_toggle_corner(plate *p)
+{
+ int i, j;
+
+ /* find an existing point to select, if any */
+ for (i = 0; i < p->n_sp; i++) {
+ subpath *sp = &p->sp[i];
+
+ for (j = 0; j < sp->n_kt; j++) {
+ knot *kt = &sp->kt[j];
+ if (kt->flags & KT_SELECTED) {
+ if (kt->flags & KT_CORNER) {
+ kt->flags |= KT_OPEN;
+ kt->flags &= ~KT_CORNER;
+ } else {
+ kt->flags &= ~KT_OPEN;
+ kt->flags |= KT_CORNER;
+ }
+ }
+ }
+ }
+}
+
+void
+plate_delete_pt(plate *p)
+{
+ int i, j;
+
+ /* find an existing point to select, if any */
+ for (i = 0; i < p->n_sp; i++) {
+ subpath *sp = &p->sp[i];
+
+ for (j = 0; j < sp->n_kt; j++) {
+ knot *kt = &sp->kt[j];
+ if (kt->flags & KT_SELECTED) {
+ memmove(kt, &kt[1], (sp->n_kt - j - 1) * sizeof(knot));
+ sp->n_kt--;
+ if (sp->n_kt < 3) sp->closed = 0;
+ j--;
+ }
+ }
+ }
+}
+
+/* Note: caller is responsible for freeing returned spiro_seg. */
+spiro_seg *
+draw_subpath(const subpath *sp, bezctx *bc)
+{
+ int n = sp->n_kt;
+ int i;
+ spiro_cp *path;
+ spiro_seg *s = NULL;
+
+ if (n > 1) {
+ path = znew(spiro_cp, n);
+
+ for (i = 0; i < n; i++) {
+ kt_flags flags = sp->kt[i].flags;
+ path[i].x = sp->kt[i].x;
+ path[i].y = sp->kt[i].y;
+ path[i].ty = !sp->closed && i == 0 ? '{' :
+ !sp->closed && i == n - 1 ? '}' :
+ (flags & KT_OPEN) ? 'o' :
+ (flags & KT_LEFT) ? '[' :
+ (flags & KT_RIGHT) ? ']' :
+ (flags & KT_CORNU) ? 'c' :
+ 'v';
+ }
+
+ s = run_spiro(path, n);
+ spiro_to_bpath(s, n, bc);
+
+ zfree(path);
+ }
+ return s;
+}
+
+int
+file_write_plate(const char *fn, const plate *p)
+{
+ FILE *f = fopen(fn, "w");
+ int i, j;
+ int st;
+
+ if (f == NULL)
+ return -1;
+ st = fprintf(f, "(plate\n");
+ for (i = 0; i < p->n_sp; i++) {
+ subpath *sp = &p->sp[i];
+ for (j = 0; j < sp->n_kt; j++) {
+ kt_flags kf = sp->kt[j].flags;
+ const char *cmd;
+
+ if (kf & KT_OPEN) cmd = "o";
+ else if (kf & KT_CORNER) cmd = "v";
+ else if (kf & KT_CORNU) cmd = "c";
+ else if (kf & KT_LEFT) cmd = "[";
+ else if (kf & KT_RIGHT) cmd = "]";
+ st = fprintf(f, " (%s %g %g)\n", cmd, sp->kt[j].x, sp->kt[j].y);
+ if (st < 0) break;
+ }
+ if (st < 0) break;
+ if (sp->closed) {
+ st = fprintf(f, " (z)\n");
+ }
+ if (st < 0) break;
+ }
+ if (st >= 0)
+ st = fprintf(f, ")\n");
+ if (st >= 0)
+ st = fclose(f);
+ return st < 0 ? -1 : 0;
+}
+
+static int
+file_read_plate_inner(sexp_reader *sr, plate *p)
+{
+ subpath *sp = NULL;
+
+ sexp_token(sr);
+ if (sr->singlechar != '(') return -1;
+ sexp_token(sr);
+ if (strcmp(sr->tokbuf, "plate")) return -1;
+ for (;;) {
+ sexp_token(sr);
+ if (sr->singlechar == ')') break;
+ else if (sr->singlechar == '(') {
+ int cmd;
+
+ sexp_token(sr);
+ cmd = sr->singlechar;
+ if (cmd == 'o' || cmd == 'v' || cmd == '[' || cmd == ']' ||
+ cmd == 'c') {
+ double x, y;
+ knot *kt;
+
+ sexp_token(sr);
+ if (!sr->is_double) return -1;
+ x = sr->d;
+ sexp_token(sr);
+ if (!sr->is_double) return -1;
+ y = sr->d;
+ sexp_token(sr);
+ if (sr->singlechar != ')') return -1;
+
+ if (sp == NULL || sp->closed)
+ sp = plate_new_sp(p);
+
+ if (sp->n_kt == sp->n_kt_max)
+ sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1);
+ kt = &sp->kt[sp->n_kt++];
+ kt->x = x;
+ kt->y = y;
+ switch (cmd) {
+ case 'o':
+ kt->flags = KT_OPEN;
+ break;
+ case '[':
+ kt->flags = KT_LEFT;
+ break;
+ case ']':
+ kt->flags = KT_RIGHT;
+ break;
+ case 'c':
+ kt->flags = KT_CORNU;
+ break;
+ default:
+ kt->flags = KT_CORNER;
+ break;
+ }
+ } else if (cmd == 'z') {
+ if (sp == NULL) return -1;
+ sp->closed = 1;
+ sexp_token(sr);
+ if (sr->singlechar != ')') return -1;
+ } else
+ return -1;
+ } else return -1;
+ }
+ return 0;
+}
+
+plate *
+file_read_plate(const char *fn)
+{
+ FILE *f = fopen(fn, "r");
+ plate *p;
+ sexp_reader sr;
+
+ if (f == NULL)
+ return NULL;
+ sr.f = f;
+ p = new_plate();
+ if (file_read_plate_inner(&sr, p)) {
+ free_plate(p);
+ p = NULL;
+ }
+ fclose(f);
+ p->mmode = MOUSE_MODE_SELECT;
+ p->motmode = MOTION_MODE_IDLE;
+ return p;
+}
diff --git a/third_party/spiro/ppedit/plate.h b/third_party/spiro/ppedit/plate.h
new file mode 100644
index 0000000..a4a1d69
--- /dev/null
+++ b/third_party/spiro/ppedit/plate.h
@@ -0,0 +1,100 @@
+typedef enum {
+ KT_OPEN = 1,
+ KT_CORNER = 2,
+ KT_LEFT = 4,
+ KT_RIGHT = 8,
+ KT_CORNU = 16,
+ KT_SELECTED = 256
+} kt_flags;
+
+typedef struct {
+ double x;
+ double y;
+ kt_flags flags;
+} knot;
+
+typedef struct {
+ int n_kt;
+ int n_kt_max;
+ knot *kt;
+ int closed;
+} subpath;
+
+typedef enum {
+ MOUSE_MODE_SELECT,
+ MOUSE_MODE_ADD_CURVE,
+ MOUSE_MODE_ADD_CORNER,
+ MOUSE_MODE_ADD_CORNU,
+ MOUSE_MODE_ADD_LEFT,
+ MOUSE_MODE_ADD_RIGHT
+} mouse_mode;
+
+typedef enum {
+ MOTION_MODE_IDLE,
+ MOTION_MODE_SELECT,
+ MOTION_MODE_MOVE
+} motion_mode;
+
+typedef struct {
+ double x0, y0;
+ const char *description;
+
+ int n_sp;
+ int n_sp_max;
+ subpath *sp;
+ mouse_mode mmode;
+ mouse_mode last_curve_mmode;
+ motion_mode motmode;
+ double sel_x0, sel_y0;
+} plate;
+
+typedef enum {
+ PRESS_MOD_SHIFT = 1,
+ PRESS_MOD_CTRL = 2,
+ PRESS_MOD_DOUBLE = 4,
+ PRESS_MOD_TRIPLE = 8
+} press_mod;
+
+plate *
+new_plate(void);
+
+void
+free_plate(plate *p);
+
+plate *
+copy_plate(const plate *p);
+
+void
+plate_select_all(plate *p, int selected);
+
+subpath *
+plate_find_selected_sp(plate *p);
+
+subpath *
+plate_new_sp(plate *p);
+
+void
+plate_press(plate *p, double x, double y, press_mod mods);
+
+void
+plate_motion_move(plate *p, double x, double y);
+
+void
+plate_motion_select(plate *p, double x, double y);
+
+void plate_unpress(plate *p);
+
+void
+plate_toggle_corner(plate *p);
+
+void
+plate_delete_pt(plate *p);
+
+spiro_seg *
+draw_subpath(const subpath *sp, bezctx *bc);
+
+int
+file_write_plate(const char *fn, const plate *p);
+
+plate *
+file_read_plate(const char *fn);
diff --git a/third_party/spiro/ppedit/ppedit.c b/third_party/spiro/ppedit/ppedit.c
new file mode 100644
index 0000000..6583930
--- /dev/null
+++ b/third_party/spiro/ppedit/ppedit.c
@@ -0,0 +1,603 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+
+#include "x3.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "zmisc.h"
+#include "bezctx.h"
+#include "bezctx_x3.h"
+#include "bezctx_ps.h"
+#include "cornu.h"
+#include "spiro.h"
+#include "plate.h"
+#include "image.h"
+
+int n_iter = 10;
+
+typedef struct {
+ const char *description;
+ plate *p;
+} undo_record;
+
+typedef struct {
+ x3widget *view;
+ const char *description;
+ plate *p;
+ int undo_n;
+ int undo_i;
+ undo_record undo_buf[16];
+ int undo_xn_state;
+
+ x3widget *undo_me;
+ x3widget *redo_me;
+
+ x3widget *show_knots_me;
+ x3widget *show_bg_me;
+ int show_knots;
+ int show_bg;
+
+ image *bg_image;
+} plate_edit;
+
+#define C1 0.55228
+static void
+draw_dot(x3dc *dc,
+ double x, double y, double r, guint32 rgba)
+{
+ x3setrgba(dc, rgba);
+ x3moveto(dc, x + r, y);
+ x3curveto(dc, x + r, y - C1 * r, x + C1 * r, y - r, x, y - r);
+ x3curveto(dc, x - C1 * r, y - r, x - r, y - C1 * r, x - r, y);
+ x3curveto(dc, x - r, y + C1 * r, x - C1 * r, y + r, x, y + r);
+ x3curveto(dc, x + C1 * r, y + r, x + r, y + C1 * r, x + r, y);
+ x3fill(dc);
+}
+
+static void
+draw_raw_rect(x3dc *dc,
+ double rx0, double ry0, double rx1, double ry1, guint32 rgba)
+{
+ x3setrgba(dc, rgba);
+ x3rectangle(dc, rx0, ry0, rx1 - rx0, ry1 - ry0);
+ x3fill(dc);
+}
+
+static void
+draw_rect(x3dc *dc,
+ double x, double y, double r, guint32 rgba)
+{
+ draw_raw_rect(dc,
+ x - r, y - r, x + r, y + r, rgba);
+}
+
+static void
+draw_half(x3dc *dc,
+ double x, double y, double r, double th, guint32 rgba)
+{
+ double c = cos(th);
+ double s = sin(th);
+
+ x3setrgba(dc, rgba);
+ x3moveto(dc, x + c * r, y + s * r);
+ x3curveto(dc,
+ x + c * r + C1 * s * r,
+ y + s * r - C1 * c * r,
+ x + s * r + C1 * c * r,
+ y - c * r + C1 * s * r,
+ x + s * r,
+ y - c * r);
+ x3curveto(dc,
+ x + s * r - C1 * c * r,
+ y - c * r - C1 * s * r,
+ x - c * r + C1 * s * r,
+ y - s * r - C1 * c * r,
+ x - c * r,
+ y - s * r);
+ x3closepath(dc);
+ x3fill(dc);
+}
+
+static void
+draw_plate(x3dc *dc,
+ plate_edit *pe)
+{
+ plate *p = pe->p;
+ int i, j;
+
+ /* find an existing point to select, if any */
+ for (i = 0; i < p->n_sp; i++) {
+ bezctx *bc = new_bezctx_x3(dc);
+ subpath *sp = &p->sp[i];
+ spiro_seg *s = draw_subpath(sp, bc);
+
+ bezctx_x3_finish(bc);
+ x3setrgba(dc, 0x000000ff);
+ x3setlinewidth(dc, 1.5);
+ x3stroke(dc);
+
+ for (j = 0; j < sp->n_kt; j++) {
+ if (pe->show_knots) {
+ knot *kt = &sp->kt[j];
+ kt_flags kf = kt->flags;
+ if ((kf & KT_SELECTED) && (kf & KT_OPEN)) {
+ draw_dot(dc, kt->x, kt->y,
+ 3, 0x000000ff);
+ draw_dot(dc, kt->x, kt->y,
+ 1.5, 0xffffffff);
+ } else if ((kf & KT_SELECTED) && (kf & KT_CORNER)) {
+ draw_rect(dc, kt->x, kt->y,
+ 3, 0x000000ff);
+ draw_rect(dc, kt->x, kt->y,
+ 1.5, 0xffffffff);
+ } else if (!(kf & KT_SELECTED) && (kf & KT_CORNER)) {
+ draw_rect(dc, kt->x, kt->y,
+ 2.5, 0x000080ff);
+ } else if ((kf & KT_SELECTED) && (kf & KT_CORNU)) {
+ draw_rect(dc, kt->x, kt->y,
+ 3, 0xc000c0ff);
+ draw_rect(dc, kt->x, kt->y,
+ 1.5, 0xffffffff);
+ } else if (!(kf & KT_SELECTED) && (kf & KT_CORNU)) {
+ draw_rect(dc, kt->x, kt->y,
+ 2.5, 0x800080ff);
+ } else if ((kf & KT_LEFT) || (kf & KT_RIGHT)) {
+ double th = 1.5708 + (s ? get_knot_th(s, j) : 0);
+ if (kf & KT_LEFT)
+ th += 3.1415926;
+ if (kf & KT_SELECTED) {
+ draw_half(dc, kt->x, kt->y,
+ 4, th, 0x000000ff);
+ draw_half(dc,
+ kt->x + sin(th), kt->y - cos(th),
+ 2, th, 0xffffffff);
+ } else {
+ draw_half(dc, kt->x, kt->y,
+ 3, th, 0x000080ff);
+ }
+ } else {
+ draw_dot(dc, kt->x, kt->y,
+ 2, 0x000080ff);
+ }
+ }
+ }
+ free_spiro(s);
+ }
+}
+
+static void
+draw_selection(x3dc *dc,
+ plate_edit *pe)
+{
+ plate *p = pe->p;
+
+ if (p->motmode == MOTION_MODE_SELECT) {
+ double rx0 = p->sel_x0;
+ double ry0 = p->sel_y0;
+ double rx1 = p->x0;
+ double ry1 = p->y0;
+ if (rx0 > rx1) {
+ double tmp = rx1;
+ rx1 = rx0;
+ rx0 = tmp;
+ }
+ if (ry0 > ry1) {
+ double tmp = ry1;
+ ry1 = ry0;
+ ry0 = tmp;
+ }
+ if (rx1 > rx0 && ry1 > ry0)
+ draw_raw_rect(dc,
+ rx0, ry0, rx1, ry1, 0x0000ff20);
+ }
+}
+
+/* Make sure there's room for at least one more undo record. */
+static void
+makeroom_undo(plate_edit *pe)
+{
+ const int undo_max = sizeof(pe->undo_buf) / sizeof(undo_record);
+
+ if (pe->undo_n == undo_max) {
+ free_plate(pe->undo_buf[0].p);
+ memmove(pe->undo_buf, pe->undo_buf + 1, (undo_max - 1) * sizeof(undo_record));
+ pe->undo_i--;
+ pe->undo_n--;
+ }
+}
+
+static void
+set_undo_menuitem(x3widget *me, const char *name, const char *desc)
+{
+ char str[256];
+
+ if (desc) {
+ sprintf(str, "%s %s", name, desc);
+ } else {
+ strcpy(str, name);
+ }
+#if 0
+ gtk_container_foreach(GTK_CONTAINER(me),
+ (GtkCallback)gtk_label_set_text,
+ str);
+#endif
+ x3setactive(me, desc != NULL);
+}
+
+static void
+set_undo_state(plate_edit *pe, const char *undo_desc, const char *redo_desc)
+{
+ set_undo_menuitem(pe->undo_me, "Undo", undo_desc);
+ set_undo_menuitem(pe->redo_me, "Redo", redo_desc);
+}
+
+static void
+begin_undo_xn(plate_edit *pe)
+{
+ int i;
+
+ if (pe->undo_xn_state != 1) {
+ for (i = pe->undo_i; i < pe->undo_n; i++)
+ free_plate(pe->undo_buf[i].p);
+ pe->undo_n = pe->undo_i;
+ makeroom_undo(pe);
+ i = pe->undo_i;
+ pe->undo_buf[i].description = pe->description;
+ pe->undo_buf[i].p = copy_plate(pe->p);
+ pe->undo_n = i + 1;
+ pe->undo_xn_state = 1;
+ }
+}
+
+static void
+dirty_undo_xn(plate_edit *pe, const char *description)
+{
+ if (pe->undo_xn_state == 0) {
+ g_warning("dirty_undo_xn: not in begin_undo_xn state");
+ begin_undo_xn(pe);
+ }
+ if (description == NULL)
+ description = pe->p->description;
+ if (pe->undo_xn_state == 1) {
+ pe->undo_i++;
+ pe->undo_xn_state = 2;
+ set_undo_state(pe, description, NULL);
+ }
+ pe->description = description;
+}
+
+static void
+begindirty_undo_xn(plate_edit *pe, const char *description)
+{
+ begin_undo_xn(pe);
+ dirty_undo_xn(pe, description);
+}
+
+static void
+end_undo_xn(plate_edit *pe)
+{
+ if (pe->undo_xn_state == 0) {
+ g_warning("end_undo_xn: not in undo xn");
+ }
+ pe->undo_xn_state = 0;
+}
+
+static int
+undo(plate_edit *pe)
+{
+ if (pe->undo_i == 0)
+ return 0;
+
+ if (pe->undo_i == pe->undo_n) {
+ makeroom_undo(pe);
+ pe->undo_buf[pe->undo_i].description = pe->description;
+ pe->undo_buf[pe->undo_i].p = pe->p;
+ pe->undo_n++;
+ } else {
+ free_plate(pe->p);
+ }
+ pe->undo_i--;
+ pe->description = pe->undo_buf[pe->undo_i].description;
+ set_undo_state(pe,
+ pe->undo_i > 0 ? pe->description : NULL,
+ pe->undo_buf[pe->undo_i + 1].description);
+ g_print("undo: %d of %d\n", pe->undo_i, pe->undo_n);
+ pe->p = copy_plate(pe->undo_buf[pe->undo_i].p);
+ return 1;
+}
+
+static int
+redo(plate_edit *pe)
+{
+ if (pe->undo_i >= pe->undo_n - 1)
+ return 0;
+ free_plate(pe->p);
+ pe->undo_i++;
+ set_undo_state(pe,
+ pe->undo_buf[pe->undo_i].description,
+ pe->undo_i < pe->undo_n - 1 ?
+ pe->undo_buf[pe->undo_i + 1].description : NULL);
+ pe->description = pe->undo_buf[pe->undo_i].description;
+ pe->p = copy_plate(pe->undo_buf[pe->undo_i].p);
+ g_print("redo: %d of %d\n", pe->undo_i, pe->undo_n);
+ return 1;
+}
+
+typedef struct {
+ x3viewclient base;
+
+ plate_edit *pe;
+} x3vc_ppe;
+
+static void
+ppedit_viewclient_draw(x3viewclient *self, x3dc *dc)
+{
+ plate_edit *pe = ((x3vc_ppe *)self)->pe;
+
+#ifdef VERBOSE
+ printf("ppedit draw\n");
+#endif
+#if 1
+ x3setrgba(dc, 0xffffffff);
+#else
+ x3setrgba(dc, rand() << 8 | 0xff);
+#endif
+ x3rectangle(dc, dc->x, dc->y, dc->width, dc->height);
+ x3fill(dc);
+
+ draw_plate(dc, pe);
+ draw_selection(dc, pe);
+}
+
+static void
+ppedit_viewclient_mouse(x3viewclient *self,
+ int button, int mods,
+ double x, double y)
+{
+ x3vc_ppe *z = (x3vc_ppe *)self;
+ plate_edit *pe = z->pe;
+ plate *p = pe->p;
+
+#ifdef VERBOSE
+ printf("ppedit mouse: %d %d %g %g\n", button, mods, x, y);
+#endif
+ if (button == 1) {
+ press_mod pm = 0;
+
+ if (mods & GDK_SHIFT_MASK) pm |= PRESS_MOD_SHIFT;
+ if (mods & GDK_CONTROL_MASK) pm |= PRESS_MOD_CTRL;
+ if (mods == GDK_2BUTTON_PRESS) pm |= PRESS_MOD_DOUBLE;
+ if (mods == GDK_3BUTTON_PRESS) pm |= PRESS_MOD_TRIPLE;
+
+ begin_undo_xn(pe);
+ p->description = NULL;
+ plate_press(p, x, y, pm);
+ if (p->description) dirty_undo_xn(pe, NULL);
+ x3view_dirty(pe->view);
+ } else if (button == -1) {
+ int need_redraw = (pe->p->motmode == MOTION_MODE_SELECT);
+
+ plate_unpress(pe->p);
+
+ if (need_redraw) x3view_dirty(pe->view);
+ } else if (button == 0 && (mods & GDK_BUTTON1_MASK)) {
+ if (p->motmode == MOTION_MODE_MOVE) {
+ plate_motion_move(p, x, y);
+ dirty_undo_xn(pe, NULL);
+ } else if (p->motmode == MOTION_MODE_SELECT) {
+ plate_motion_select(p, x, y);
+ }
+ x3view_dirty(pe->view);
+ }
+}
+
+static int
+ppedit_viewclient_key(x3viewclient *self, char *keyname, int mods, int key)
+{
+ x3vc_ppe *z = (x3vc_ppe *)self;
+ plate_edit *pe = z->pe;
+ double dx = 0, dy = 0;
+ int did_something = 0;
+
+ if (!strcmp(keyname, "Left"))
+ dx = -1;
+ else if (!strcmp(keyname, "Right"))
+ dx = 1;
+ else if (!strcmp(keyname, "Up"))
+ dy = -1;
+ else if (!strcmp(keyname, "Down"))
+ dy = 1;
+ if (mods & X3_SHIFT_MASK) {
+ dx *= 10;
+ dy *= 10;
+ } else if (mods & X3_CONTROL_MASK) {
+ dx *= .1;
+ dy *= .1;
+ }
+ if (dx != 0 || dy != 0) {
+ begindirty_undo_xn(pe, "Keyboard move");
+ plate_motion_move(pe->p, pe->p->x0 + dx, pe->p->y0 + dy);
+ end_undo_xn(pe);
+ did_something = TRUE;
+ }
+ if (did_something) {
+ x3view_dirty(pe->view);
+ return 1;
+ } else
+ return 0;
+}
+
+x3viewclient *ppedit_viewclient(plate_edit *pe)
+{
+ x3vc_ppe *result = (x3vc_ppe *)malloc(sizeof(x3vc_ppe));
+
+ x3viewclient_init(&result->base);
+ result->base.draw = ppedit_viewclient_draw;
+ result->base.mouse = ppedit_viewclient_mouse;
+ result->base.key = ppedit_viewclient_key;
+ result->pe = pe;
+
+ return &result->base;
+}
+
+int
+print_func(plate_edit *pe)
+{
+ plate *p = pe->p;
+ int i;
+ FILE *f = fopen("/tmp/foo.ps", "w");
+ bezctx *bc = new_bezctx_ps(f);
+
+ fputs(ps_prolog, f);
+ for (i = 0; i < p->n_sp; i++) {
+ subpath *sp = &p->sp[i];
+ free_spiro(draw_subpath(sp, bc));
+ }
+ bezctx_ps_close(bc);
+ fputs(ps_postlog, f);
+ fclose(f);
+ return TRUE;
+}
+
+int
+ppedit_callback(x3widget *w, void *data,
+ char *cmd, char *what, char *arg, void *more)
+{
+ plate_edit *pe = (plate_edit *)data;
+ printf("my callback: cmd=\"%s\", what=\"%s\", arg=\"%s\"\n",
+ cmd, what ? what : "(null)", arg ? arg : "(null)");
+
+ if (!strcmp(cmd, "quit")) {
+ gtk_main_quit();
+ } else if (!strcmp(cmd, "save")) {
+ file_write_plate("plate", pe->p);
+ } else if (!strcmp(cmd, "khid")) {
+ pe->show_knots = !pe->show_knots;
+ x3view_dirty(pe->view);
+ } else if (!strcmp(cmd, "bghd")) {
+ pe->show_bg = !pe->show_bg;
+ x3view_dirty(pe->view);
+ } else if (!strcmp(cmd, "prin")) {
+ return print_func(pe);
+ } else if (!strcmp(cmd, "undo")) {
+ undo(pe);
+ x3view_dirty(pe->view);
+ } else if (!strcmp(cmd, "redo")) {
+ redo(pe);
+ x3view_dirty(pe->view);
+ } else if (!strcmp(cmd, "togc")) {
+ begindirty_undo_xn(pe, "Toggle Corner");
+ plate_toggle_corner(pe->p);
+ end_undo_xn(pe);
+ x3view_dirty(pe->view);
+ } else if (!strcmp(cmd, "delp")) {
+ begindirty_undo_xn(pe, "Delete Point");
+ plate_delete_pt(pe->p);
+ end_undo_xn(pe);
+ x3view_dirty(pe->view);
+ } else if (!strcmp(cmd, "mods")) {
+ pe->p->mmode = MOUSE_MODE_SELECT;
+ } else if (!strcmp(cmd, "modo")) {
+ pe->p->mmode = MOUSE_MODE_ADD_CURVE;
+ } else if (!strcmp(cmd, "modv")) {
+ pe->p->mmode = MOUSE_MODE_ADD_CORNER;
+ } else if (!strcmp(cmd, "modl")) {
+ pe->p->mmode = MOUSE_MODE_ADD_LEFT;
+ } else if (!strcmp(cmd, "modr")) {
+ pe->p->mmode = MOUSE_MODE_ADD_RIGHT;
+ } else if (!strcmp(cmd, "modc")) {
+ pe->p->mmode = MOUSE_MODE_ADD_CORNU;
+ }
+ return 1;
+}
+
+static void
+create_mainwin(plate_edit *pe)
+{
+ x3widget *mainwin;
+ x3widget *view;
+ x3widget *menu;
+ void *data = pe;
+ x3viewflags viewflags = x3view_click | x3view_hover | x3view_key |
+ x3view_2d | x3view_scroll;
+
+ mainwin = x3window(x3window_main, "ppedit", ppedit_callback, data);
+
+ menu = x3menu(mainwin, "File");
+
+ x3menuitem(menu, "Save", "save", "<cmd>s");
+ x3menuitem(menu, "Print", "prin", "<cmd>p");
+ x3menuitem(menu, "Quit", "quit", "<cmd>q");
+
+ menu = x3menu(mainwin, "Edit");
+
+ pe->undo_me = x3menuitem(menu, "Undo", "undo", "<cmd>z");
+ pe->redo_me = x3menuitem(menu, "Redo", "redo", "<cmd>y");
+
+ //set_undo_state(p, NULL, NULL);
+ x3menuitem(menu, "Toggle Corner", "togc", "<cmd>t");
+ x3menuitem(menu, "Delete Point", "delp", "<cmd>d");
+ x3menuitem(menu, "Selection Mode", "mods", "1");
+ x3menuitem(menu, "Add Curve Mode", "modo", "2");
+ x3menuitem(menu, "Add Corner Mode", "modv", "3");
+ x3menuitem(menu, "Add Left Mode", "modl", "4");
+ x3menuitem(menu, "Add Right Mode", "modr", "5");
+ x3menuitem(menu, "Add G2 point Mode", "modc", "6");
+
+ menu = x3menu(mainwin, "View");
+
+ pe->show_knots_me = x3menuitem(menu, "Hide Knots", "khid", "<cmd>k");
+ pe->show_bg_me = x3menuitem(menu, "Hide BG", "bghd", "<cmd>b");
+
+ pe->view = x3view(mainwin, viewflags, ppedit_viewclient(pe));
+
+#ifdef X3_GTK
+ gtk_window_set_default_size(GTK_WINDOW(mainwin->widget), 512, 512);
+#endif
+
+ x3_window_show(mainwin);
+}
+
+int main(int argc, char **argv)
+{
+ plate_edit pe;
+ plate *p = NULL;
+ x3init(&argc, &argv);
+ char *reason;
+
+ if (argc > 1)
+ p = file_read_plate(argv[1]);
+ if (p == NULL)
+ p = new_plate();
+ pe.p = p;
+ pe.undo_n = 0;
+ pe.undo_i = 0;
+ pe.undo_xn_state = 0;
+ pe.show_knots = 1;
+ pe.show_bg = 1;
+ pe.bg_image = load_image_file("/tmp/foo.ppm", &reason);
+ create_mainwin(&pe);
+ x3main();
+ return 0;
+}
diff --git a/third_party/spiro/ppedit/ppedit_gtk1.c b/third_party/spiro/ppedit/ppedit_gtk1.c
new file mode 100644
index 0000000..b81299e
--- /dev/null
+++ b/third_party/spiro/ppedit/ppedit_gtk1.c
@@ -0,0 +1,930 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <libart_lgpl/libart.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "zmisc.h"
+#include "bezctx.h"
+#include "bezctx_libart.h"
+#include "bezctx_ps.h"
+#include "cornu.h"
+#include "spiro.h"
+#include "plate.h"
+#include "image.h"
+
+int n_iter = 10;
+
+typedef struct {
+ const char *description;
+ plate *p;
+} undo_record;
+
+typedef struct {
+ GtkWidget *da;
+ const char *description;
+ plate *p;
+ int undo_n;
+ int undo_i;
+ undo_record undo_buf[16];
+ int undo_xn_state;
+
+ GtkWidget *undo_me;
+ GtkWidget *redo_me;
+
+ GtkWidget *show_knots_me;
+ GtkWidget *show_bg_me;
+ int show_knots;
+ int show_bg;
+
+ image *bg_image;
+} plate_edit;
+
+int
+quit_func(GtkWidget *widget, gpointer dummy)
+{
+ gtk_main_quit();
+ return TRUE;
+}
+
+#define C1 0.55228
+static void
+draw_dot(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride,
+ double x, double y, double r, guint32 rgba)
+{
+ ArtBpath bp[6];
+ ArtVpath *vp;
+ ArtSVP *svp;
+
+ bp[0].code = ART_MOVETO;
+ bp[0].x3 = x + r;
+ bp[0].y3 = y;
+ bp[1].code = ART_CURVETO;
+ bp[1].x1 = x + r;
+ bp[1].y1 = y - C1 * r;
+ bp[1].x2 = x + C1 * r;
+ bp[1].y2 = y - r;
+ bp[1].x3 = x;
+ bp[1].y3 = y - r;
+ bp[2].code = ART_CURVETO;
+ bp[2].x1 = x - C1 * r;
+ bp[2].y1 = y - r;
+ bp[2].x2 = x - r;
+ bp[2].y2 = y - C1 * r;
+ bp[2].x3 = x - r;
+ bp[2].y3 = y;
+ bp[3].code = ART_CURVETO;
+ bp[3].x1 = x - r;
+ bp[3].y1 = y + C1 * r;
+ bp[3].x2 = x - C1 * r;
+ bp[3].y2 = y + r;
+ bp[3].x3 = x;
+ bp[3].y3 = y + r;
+ bp[4].code = ART_CURVETO;
+ bp[4].x1 = x + C1 * r;
+ bp[4].y1 = y + r;
+ bp[4].x2 = x + r;
+ bp[4].y2 = y + C1 * r;
+ bp[4].x3 = x + r;
+ bp[4].y3 = y;
+ bp[5].code = ART_END;
+
+ vp = art_bez_path_to_vec(bp, 0.25);
+ svp = art_svp_from_vpath(vp);
+ art_free(vp);
+
+ art_rgb_svp_alpha(svp, x0, y0, x1, y1, rgba, buf, rowstride, NULL);
+ art_svp_free(svp);
+}
+
+static void
+draw_raw_rect(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride,
+ double rx0, double ry0, double rx1, double ry1, guint32 rgba)
+{
+ ArtVpath vp[6];
+ ArtSVP *svp;
+
+ vp[0].code = ART_MOVETO;
+ vp[0].x = rx0;
+ vp[0].y = ry1;
+ vp[1].code = ART_LINETO;
+ vp[1].x = rx1;
+ vp[1].y = ry1;
+ vp[2].code = ART_LINETO;
+ vp[2].x = rx1;
+ vp[2].y = ry0;
+ vp[3].code = ART_LINETO;
+ vp[3].x = rx0;
+ vp[3].y = ry0;
+ vp[4].code = ART_LINETO;
+ vp[4].x = rx0;
+ vp[4].y = ry1;
+ vp[5].code = ART_END;
+
+ svp = art_svp_from_vpath(vp);
+
+ art_rgb_svp_alpha(svp, x0, y0, x1, y1, rgba, buf, rowstride, NULL);
+ art_svp_free(svp);
+}
+
+static void
+draw_rect(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride,
+ double x, double y, double r, guint32 rgba)
+{
+ draw_raw_rect(buf, x0, y0, x1, y1, rowstride,
+ x - r, y - r, x + r, y + r, rgba);
+}
+
+static void
+draw_half(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride,
+ double x, double y, double r, double th, guint32 rgba)
+{
+ ArtBpath bp[6];
+ ArtVpath *vp;
+ ArtSVP *svp;
+ double c = cos(th);
+ double s = sin(th);
+
+ bp[0].code = ART_MOVETO;
+ bp[0].x3 = x + c * r;
+ bp[0].y3 = y + s * r;
+ bp[1].code = ART_CURVETO;
+ bp[1].x1 = x + c * r + C1 * s * r;
+ bp[1].y1 = y + s * r - C1 * c * r;
+ bp[1].x2 = x + s * r + C1 * c * r;
+ bp[1].y2 = y - c * r + C1 * s * r;
+ bp[1].x3 = x + s * r;
+ bp[1].y3 = y - c * r;
+ bp[2].code = ART_CURVETO;
+ bp[2].x1 = x + s * r - C1 * c * r;
+ bp[2].y1 = y - c * r - C1 * s * r;
+ bp[2].x2 = x - c * r + C1 * s * r;
+ bp[2].y2 = y - s * r - C1 * c * r;
+ bp[2].x3 = x - c * r;
+ bp[2].y3 = y - s * r;
+ bp[3].code = ART_LINETO;
+ bp[3].x3 = x + c * r;
+ bp[3].y3 = y + s * r;
+ bp[4].code = ART_END;
+
+ vp = art_bez_path_to_vec(bp, 0.25);
+ svp = art_svp_from_vpath(vp);
+ art_free(vp);
+
+ art_rgb_svp_alpha(svp, x0, y0, x1, y1, rgba, buf, rowstride, NULL);
+ art_svp_free(svp);
+}
+
+static ArtVpath *
+bezctx_to_vpath(bezctx *bc)
+{
+ ArtBpath *bp = bezctx_to_bpath(bc);
+ ArtVpath *vp = art_bez_path_to_vec(bp, .25);
+
+ g_free(bp);
+ if (vp[0].code == ART_END || vp[1].code == ART_END) {
+ g_free(vp);
+ vp = NULL;
+ }
+ return vp;
+}
+
+static void
+draw_plate(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride,
+ plate_edit *pe)
+{
+ plate *p = pe->p;
+ int i, j;
+
+ /* find an existing point to select, if any */
+ for (i = 0; i < p->n_sp; i++) {
+ bezctx *bc = new_bezctx_libart();
+ subpath *sp = &p->sp[i];
+ spiro_seg *s = draw_subpath(sp, bc);
+ ArtVpath *vp = bezctx_to_vpath(bc);
+
+ if (vp != NULL) {
+ ArtSVP *svp = art_svp_vpath_stroke(vp, ART_PATH_STROKE_JOIN_MITER,
+ ART_PATH_STROKE_CAP_BUTT,
+ 1.5, 4.0, 0.25);
+
+ art_free(vp);
+ art_rgb_svp_alpha(svp, x0, y0, x1, y1, 0x000000ff, buf, rowstride,
+ NULL);
+ art_svp_free(svp);
+ }
+
+ for (j = 0; j < sp->n_kt; j++) {
+ if (pe->show_knots) {
+ knot *kt = &sp->kt[j];
+ kt_flags kf = kt->flags;
+ if ((kf & KT_SELECTED) && (kf & KT_OPEN)) {
+ draw_dot(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y,
+ 3, 0x000000ff);
+ draw_dot(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y,
+ 1.5, 0xffffffff);
+ } else if ((kf & KT_SELECTED) && (kf & KT_CORNER)) {
+ draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y,
+ 3, 0x000000ff);
+ draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y,
+ 1.5, 0xffffffff);
+ } else if (!(kf & KT_SELECTED) && (kf & KT_CORNER)) {
+ draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y,
+ 2.5, 0x000080ff);
+ } else if ((kf & KT_SELECTED) && (kf & KT_CORNU)) {
+ draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y,
+ 3, 0xc000c0ff);
+ draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y,
+ 1.5, 0xffffffff);
+ } else if (!(kf & KT_SELECTED) && (kf & KT_CORNU)) {
+ draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y,
+ 2.5, 0x800080ff);
+ } else if ((kf & KT_LEFT) || (kf & KT_RIGHT)) {
+ double th = 1.5708 + (s ? get_knot_th(s, j) : 0);
+ if (kf & KT_LEFT)
+ th += 3.1415926;
+ if (kf & KT_SELECTED) {
+ draw_half(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y,
+ 4, th, 0x000000ff);
+ draw_half(buf, x0, y0, x1, y1, rowstride,
+ kt->x + sin(th), kt->y - cos(th),
+ 2, th, 0xffffffff);
+ } else {
+ draw_half(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y,
+ 3, th, 0x000080ff);
+ }
+ } else {
+ draw_dot(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y,
+ 2, 0x000080ff);
+ }
+ }
+ }
+ free_spiro(s);
+ }
+}
+
+static void
+draw_selection(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride,
+ plate_edit *pe)
+{
+ plate *p = pe->p;
+
+ if (p->motmode == MOTION_MODE_SELECT) {
+ double rx0 = p->sel_x0;
+ double ry0 = p->sel_y0;
+ double rx1 = p->x0;
+ double ry1 = p->y0;
+ if (rx0 > rx1) {
+ double tmp = rx1;
+ rx1 = rx0;
+ rx0 = tmp;
+ }
+ if (ry0 > ry1) {
+ double tmp = ry1;
+ ry1 = ry0;
+ ry0 = tmp;
+ }
+ if (rx1 > rx0 && ry1 > ry0)
+ draw_raw_rect(buf, x0, y0, x1, y1, rowstride,
+ rx0, ry0, rx1, ry1, 0x0000ff20);
+ }
+}
+
+static void
+render_bg_layer(guchar *buf, int rowstride, int x0, int y0, int x1, int y1,
+ plate_edit *pe)
+{
+ const double affine[6] = { 1, 0, 0, 1, 0, 0 };
+
+ if (pe->show_bg && pe->bg_image)
+ render_image(pe->bg_image, affine,
+ buf, rowstride, x0, y0, x1, y1);
+ else
+ memset(buf, 255, (y1 - y0) * rowstride);
+}
+
+static gint
+data_expose (GtkWidget *widget, GdkEventExpose *event, void *data)
+{
+ plate_edit *pe = (plate_edit *)data;
+ int x0 = event->area.x;
+ int y0 = event->area.y;
+ int width = event->area.width;
+ int height = event->area.height;
+ guchar *rgb;
+ int rowstride = (width * 3 + 3) & -4;
+
+ rgb = g_new (guchar, event->area.height * rowstride);
+
+ render_bg_layer(rgb, rowstride, x0, y0, x0 + width, y0 + height, pe);
+
+ draw_plate(rgb, x0, y0, x0 + width, y0 + height, rowstride, pe);
+
+ draw_selection(rgb, x0, y0, x0 + width, y0 + height, rowstride, pe);
+
+ gdk_draw_rgb_image(widget->window,
+ widget->style->black_gc,
+ x0, y0, width, height,
+ GDK_RGB_DITHER_NONE, rgb,
+ rowstride);
+ g_free(rgb);
+ return FALSE;
+}
+
+/* Make sure there's room for at least one more undo record. */
+static void
+makeroom_undo(plate_edit *pe)
+{
+ const int undo_max = sizeof(pe->undo_buf) / sizeof(undo_record);
+
+ if (pe->undo_n == undo_max) {
+ free_plate(pe->undo_buf[0].p);
+ memmove(pe->undo_buf, pe->undo_buf + 1, (undo_max - 1) * sizeof(undo_record));
+ pe->undo_i--;
+ pe->undo_n--;
+ }
+}
+
+static void
+set_undo_menuitem(GtkWidget *me, const char *name, const char *desc)
+{
+ char str[256];
+
+ if (desc) {
+ sprintf(str, "%s %s", name, desc);
+ } else {
+ strcpy(str, name);
+ }
+ gtk_container_foreach(GTK_CONTAINER(me),
+ (GtkCallback)gtk_label_set_text,
+ str);
+ gtk_widget_set_sensitive(me, desc != NULL);
+}
+
+static void
+set_undo_state(plate_edit *pe, const char *undo_desc, const char *redo_desc)
+{
+ set_undo_menuitem(pe->undo_me, "Undo", undo_desc);
+ set_undo_menuitem(pe->redo_me, "Redo", redo_desc);
+}
+
+static void
+begin_undo_xn(plate_edit *pe)
+{
+ int i;
+
+ if (pe->undo_xn_state != 1) {
+ for (i = pe->undo_i; i < pe->undo_n; i++)
+ free_plate(pe->undo_buf[i].p);
+ pe->undo_n = pe->undo_i;
+ makeroom_undo(pe);
+ i = pe->undo_i;
+ pe->undo_buf[i].description = pe->description;
+ pe->undo_buf[i].p = copy_plate(pe->p);
+ pe->undo_n = i + 1;
+ pe->undo_xn_state = 1;
+ }
+}
+
+static void
+dirty_undo_xn(plate_edit *pe, const char *description)
+{
+ if (pe->undo_xn_state == 0) {
+ g_warning("dirty_undo_xn: not in begin_undo_xn state");
+ begin_undo_xn(pe);
+ }
+ if (description == NULL)
+ description = pe->p->description;
+ if (pe->undo_xn_state == 1) {
+ pe->undo_i++;
+ pe->undo_xn_state = 2;
+ set_undo_state(pe, description, NULL);
+ }
+ pe->description = description;
+}
+
+static void
+begindirty_undo_xn(plate_edit *pe, const char *description)
+{
+ begin_undo_xn(pe);
+ dirty_undo_xn(pe, description);
+}
+
+static void
+end_undo_xn(plate_edit *pe)
+{
+ if (pe->undo_xn_state == 0) {
+ g_warning("end_undo_xn: not in undo xn");
+ }
+ pe->undo_xn_state = 0;
+}
+
+static int
+undo(plate_edit *pe)
+{
+ if (pe->undo_i == 0)
+ return 0;
+
+ if (pe->undo_i == pe->undo_n) {
+ makeroom_undo(pe);
+ pe->undo_buf[pe->undo_i].description = pe->description;
+ pe->undo_buf[pe->undo_i].p = pe->p;
+ pe->undo_n++;
+ } else {
+ free_plate(pe->p);
+ }
+ pe->undo_i--;
+ pe->description = pe->undo_buf[pe->undo_i].description;
+ set_undo_state(pe,
+ pe->undo_i > 0 ? pe->description : NULL,
+ pe->undo_buf[pe->undo_i + 1].description);
+ g_print("undo: %d of %d\n", pe->undo_i, pe->undo_n);
+ pe->p = copy_plate(pe->undo_buf[pe->undo_i].p);
+ return 1;
+}
+
+static int
+redo(plate_edit *pe)
+{
+ if (pe->undo_i >= pe->undo_n - 1)
+ return 0;
+ free_plate(pe->p);
+ pe->undo_i++;
+ set_undo_state(pe,
+ pe->undo_buf[pe->undo_i].description,
+ pe->undo_i < pe->undo_n - 1 ?
+ pe->undo_buf[pe->undo_i + 1].description : NULL);
+ pe->description = pe->undo_buf[pe->undo_i].description;
+ pe->p = copy_plate(pe->undo_buf[pe->undo_i].p);
+ g_print("redo: %d of %d\n", pe->undo_i, pe->undo_n);
+ return 1;
+}
+
+static gint
+data_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+ plate *p = pe->p;
+ double x, y;
+ press_mod mods = 0;
+
+#define noVERBOSE
+#ifdef VERBOSE
+ g_print ("button press %f %f %f %d\n",
+ event->x, event->y, event->pressure, event->type);
+
+#endif
+ x = event->x;
+ y = event->y;
+ if (event->state & GDK_SHIFT_MASK) mods |= PRESS_MOD_SHIFT;
+ if (event->state & GDK_CONTROL_MASK) mods |= PRESS_MOD_CTRL;
+ if (event->type == GDK_2BUTTON_PRESS) mods |= PRESS_MOD_DOUBLE;
+ if (event->type == GDK_3BUTTON_PRESS) mods |= PRESS_MOD_TRIPLE;
+
+ begin_undo_xn(pe);
+ p->description = NULL;
+ plate_press(p, x, y, mods);
+ if (p->description) dirty_undo_xn(pe, NULL);
+ gtk_widget_queue_draw(widget);
+
+ return TRUE;
+}
+
+static gint
+data_motion_move (GtkWidget *widget, GdkEventMotion *event, plate_edit *pe)
+{
+ double x, y;
+ x = event->x;
+ y = event->y;
+
+ plate_motion_move(pe->p, x, y);
+ dirty_undo_xn(pe, NULL);
+
+ gtk_widget_queue_draw(widget);
+
+ return TRUE;
+}
+
+static gint
+data_motion_select (GtkWidget *widget, GdkEventMotion *event, plate_edit *pe)
+{
+ double x, y;
+
+ x = event->x;
+ y = event->y;
+
+ plate_motion_select(pe->p, x, y);
+ gtk_widget_queue_draw(widget);
+
+ return TRUE;
+}
+
+static gint
+data_motion (GtkWidget *widget, GdkEventMotion *event, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+#ifdef VERBOSE
+ g_print ("motion %f %f %f\n", event->x, event->y, event->pressure);
+
+#endif
+ if (pe->p->motmode == MOTION_MODE_MOVE)
+ return data_motion_move(widget, event, pe);
+ else if (pe->p->motmode == MOTION_MODE_SELECT)
+ return data_motion_select(widget, event, pe);
+ return TRUE;
+}
+
+static gint
+data_button_release (GtkWidget *widget, GdkEventMotion *event, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+ int need_redraw;
+
+ need_redraw = (pe->p->motmode == MOTION_MODE_SELECT);
+
+ plate_unpress(pe->p);
+
+ gtk_widget_queue_draw(widget);
+
+ return TRUE;
+}
+
+static gboolean
+key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+ int delta = event->state & 4 ? 10 : 1;
+ int old_n_iter = n_iter;
+ double dx = 0, dy = 0;
+ gboolean did_something = FALSE;
+
+ g_print("key press %d %s %d\n", event->keyval, event->string, event->state);
+
+ if (event->keyval == '<') {
+ did_something = TRUE;
+ n_iter -= delta;
+ } else if (event->keyval == '>') {
+ n_iter += delta;
+ }
+ if (n_iter < 0) n_iter = 0;
+ if (n_iter != old_n_iter)
+ g_print("n_iter = %d\n", n_iter);
+
+ if (event->keyval == GDK_Left)
+ dx = -1;
+ else if (event->keyval == GDK_Right)
+ dx = 1;
+ else if (event->keyval == GDK_Up)
+ dy = -1;
+ else if (event->keyval == GDK_Down)
+ dy = 1;
+ if (event->state & GDK_SHIFT_MASK) {
+ dx *= 10;
+ dy *= 10;
+ } else if (event->state & GDK_CONTROL_MASK) {
+ dx *= .1;
+ dy *= .1;
+ }
+ if (dx != 0 || dy != 0) {
+ begindirty_undo_xn(pe, "Keyboard move");
+ plate_motion_move(pe->p, pe->p->x0 + dx, pe->p->y0 + dy);
+ end_undo_xn(pe);
+ did_something = TRUE;
+ }
+
+ if (did_something) {
+ gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key-press-event");
+ gtk_widget_queue_draw(widget);
+ }
+
+ return did_something;
+}
+
+static gint
+toggle_corner_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ begindirty_undo_xn(pe, "Toggle Corner");
+ plate_toggle_corner(pe->p);
+ end_undo_xn(pe);
+ gtk_widget_queue_draw(pe->da);
+
+ return TRUE;
+}
+
+static gint
+delete_pt_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ begindirty_undo_xn(pe, "Delete Point");
+ plate_delete_pt(pe->p);
+ end_undo_xn(pe);
+ gtk_widget_queue_draw(pe->da);
+
+ return TRUE;
+}
+
+static gint
+set_select_mode_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ pe->p->mmode = MOUSE_MODE_SELECT;
+ return TRUE;
+}
+
+static gint
+set_curve_mode_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ pe->p->mmode = MOUSE_MODE_ADD_CURVE;
+ pe->p->last_curve_mmode = pe->p->mmode;
+ return TRUE;
+}
+
+static gint
+set_corner_mode_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ pe->p->mmode = MOUSE_MODE_ADD_CORNER;
+ return TRUE;
+}
+
+static gint
+set_left_mode_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ pe->p->mmode = MOUSE_MODE_ADD_LEFT;
+ return TRUE;
+}
+
+static gint
+set_right_mode_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ pe->p->mmode = MOUSE_MODE_ADD_RIGHT;
+ return TRUE;
+}
+
+static gint
+set_cornu_mode_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ pe->p->mmode = MOUSE_MODE_ADD_CORNU;
+ pe->p->last_curve_mmode = pe->p->mmode;
+ return TRUE;
+}
+
+static gint
+undo_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ undo(pe);
+ gtk_widget_queue_draw(pe->da);
+
+ return TRUE;
+}
+
+static gint
+redo_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ redo(pe);
+ gtk_widget_queue_draw(pe->da);
+
+ return TRUE;
+}
+
+static gint
+save_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ file_write_plate("plate", pe->p);
+
+ return TRUE;
+}
+
+static gint
+toggle_show_knots_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ pe->show_knots = !pe->show_knots;
+ gtk_container_foreach(GTK_CONTAINER(pe->show_knots_me),
+ (GtkCallback)gtk_label_set_text,
+ pe->show_knots ? "Hide Knots" : "Show Knots");
+ gtk_widget_queue_draw(pe->da);
+ return TRUE;
+}
+
+static gint
+toggle_show_bg_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+
+ pe->show_bg = !pe->show_bg;
+ gtk_container_foreach(GTK_CONTAINER(pe->show_bg_me),
+ (GtkCallback)gtk_label_set_text,
+ pe->show_bg ? "Hide BG" : "Show BG");
+ gtk_widget_queue_draw(pe->da);
+ return TRUE;
+}
+
+static gint
+print_func(GtkWidget *widget, gpointer data)
+{
+ plate_edit *pe = (plate_edit *)data;
+ plate *p = pe->p;
+ int i;
+ FILE *f = fopen("/tmp/foo.ps", "w");
+ bezctx *bc = new_bezctx_ps(f);
+
+ fputs(ps_prolog, f);
+ for (i = 0; i < p->n_sp; i++) {
+ subpath *sp = &p->sp[i];
+ free_spiro(draw_subpath(sp, bc));
+ }
+ bezctx_ps_close(bc);
+ fputs(ps_postlog, f);
+ fclose(f);
+ return TRUE;
+}
+
+static GtkWidget *
+add_menuitem(GtkWidget *menu, const char *name, GtkSignalFunc callback,
+ gpointer callback_data, GtkAccelGroup *ag, const char *accel)
+{
+ GtkWidget *menuitem;
+
+ menuitem = gtk_menu_item_new_with_label(name);
+ gtk_menu_append(GTK_MENU(menu), menuitem);
+ gtk_widget_show(menuitem);
+ if (accel != NULL) {
+ guint accel_key, accel_mods;
+
+ gtk_accelerator_parse(accel, &accel_key, &accel_mods);
+ gtk_widget_add_accelerator(menuitem, "activate", ag,
+ accel_key, accel_mods, GTK_ACCEL_VISIBLE);
+ }
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ (GtkSignalFunc)callback, callback_data);
+ return (menuitem);
+}
+
+static void
+create_mainwin(plate_edit *p)
+{
+ GtkWidget *mainwin;
+ GtkWidget *eb;
+ GtkWidget *da;
+ GtkWidget *vbox;
+ GtkWidget *menubar;
+ GtkWidget *menu;
+ GtkWidget *menuitem;
+ GtkAccelGroup *ag;
+ void *data = p;
+
+ mainwin = gtk_widget_new(gtk_window_get_type(),
+ "GtkWindow::type", GTK_WINDOW_TOPLEVEL,
+ "GtkWindow::title", "pattern plate editor",
+ NULL);
+ gtk_signal_connect(GTK_OBJECT(mainwin), "destroy",
+ (GtkSignalFunc)quit_func, NULL);
+
+ vbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(mainwin), vbox);
+
+ menubar = gtk_menu_bar_new();
+ ag = gtk_accel_group_new();
+ gtk_window_add_accel_group(GTK_WINDOW(mainwin), ag);
+ gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
+
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label("File");
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+ gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem);
+ gtk_widget_show(menuitem);
+ gtk_menu_set_accel_group(GTK_MENU(menu), ag);
+ add_menuitem(menu, "Save", (GtkSignalFunc)save_func, data, ag, "<ctrl>S");
+ add_menuitem(menu, "Quit", (GtkSignalFunc)quit_func, data, ag, "<ctrl>Q");
+ add_menuitem(menu, "Print", (GtkSignalFunc)print_func, data, ag, "<ctrl>P");
+
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label("Edit");
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+ gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem);
+ gtk_widget_show(menuitem);
+ gtk_menu_set_accel_group(GTK_MENU(menu), ag);
+ p->undo_me = add_menuitem(menu, "Undo", (GtkSignalFunc)undo_func, data, ag, "<ctrl>Z");
+ p->redo_me = add_menuitem(menu, "Redo", (GtkSignalFunc)redo_func, data, ag, "<ctrl>Y");
+ set_undo_state(p, NULL, NULL);
+ add_menuitem(menu, "Toggle Corner", (GtkSignalFunc)toggle_corner_func, data, ag, "<ctrl>T");
+ add_menuitem(menu, "Delete Point", (GtkSignalFunc)delete_pt_func, data, ag, "<ctrl>D");
+ add_menuitem(menu, "Selection Mode", (GtkSignalFunc)set_select_mode_func, data, ag, "1");
+ add_menuitem(menu, "Add Curve Mode", (GtkSignalFunc)set_curve_mode_func, data, ag, "2");
+ add_menuitem(menu, "Add Corner Mode", (GtkSignalFunc)set_corner_mode_func, data, ag, "3");
+ add_menuitem(menu, "Add Left Mode", (GtkSignalFunc)set_left_mode_func, data, ag, "4");
+ add_menuitem(menu, "Add Right Mode", (GtkSignalFunc)set_right_mode_func, data, ag, "5");
+ add_menuitem(menu, "Add Cornu Mode", (GtkSignalFunc)set_cornu_mode_func, data, ag, "6");
+
+
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label("View");
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+ gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem);
+ gtk_widget_show(menuitem);
+ gtk_menu_set_accel_group(GTK_MENU(menu), ag);
+ p->show_knots_me = add_menuitem(menu, "Hide Knots",
+ (GtkSignalFunc)toggle_show_knots_func,
+ data, ag, "<ctrl>K");
+ p->show_bg_me = add_menuitem(menu, "Hide BG",
+ (GtkSignalFunc)toggle_show_bg_func,
+ data, ag, "<ctrl>B");
+
+ eb = gtk_event_box_new ();
+ GTK_WIDGET_SET_FLAGS(eb, GTK_CAN_FOCUS);
+ gtk_box_pack_start(GTK_BOX(vbox), eb, TRUE, TRUE, 0);
+ gtk_widget_set_extension_events (eb, GDK_EXTENSION_EVENTS_ALL);
+ gtk_signal_connect(GTK_OBJECT (eb), "button-press-event",
+ (GtkSignalFunc) data_button_press, data);
+ gtk_signal_connect(GTK_OBJECT (eb), "motion-notify-event",
+ (GtkSignalFunc) data_motion, data);
+ gtk_signal_connect(GTK_OBJECT (eb), "button-release-event",
+ (GtkSignalFunc) data_button_release, data);
+ gtk_signal_connect(GTK_OBJECT(eb), "key-press-event",
+ (GtkSignalFunc)key_press, data);
+
+ da = gtk_drawing_area_new();
+ p->da = da;
+ gtk_window_set_default_size(GTK_WINDOW(mainwin), 512, 512);
+ gtk_container_add(GTK_CONTAINER(eb), da);
+ gtk_signal_connect(GTK_OBJECT (da), "expose-event",
+ (GtkSignalFunc) data_expose, data);
+#if 0
+ gtk_widget_set_double_buffered(da, FALSE);
+#endif
+
+ gtk_widget_grab_focus(eb);
+ gtk_widget_show(da);
+ gtk_widget_show(eb);
+ gtk_widget_show(menubar);
+ gtk_widget_show(vbox);
+ gtk_widget_show(mainwin);
+}
+
+int main(int argc, char **argv)
+{
+ plate_edit pe;
+ plate *p = NULL;
+ gtk_init(&argc, &argv);
+ gtk_widget_set_default_colormap(gdk_rgb_get_cmap());
+ gtk_widget_set_default_visual(gdk_rgb_get_visual());
+ char *reason;
+
+ if (argc > 1)
+ p = file_read_plate(argv[1]);
+ if (p == NULL)
+ p = new_plate();
+ pe.p = p;
+ pe.undo_n = 0;
+ pe.undo_i = 0;
+ pe.undo_xn_state = 0;
+ pe.show_knots = 1;
+ pe.show_bg = 1;
+ pe.bg_image = load_image_file("/tmp/foo.ppm", &reason);
+ create_mainwin(&pe);
+ gtk_main();
+ return 0;
+}
diff --git a/third_party/spiro/ppedit/sexp.c b/third_party/spiro/ppedit/sexp.c
new file mode 100644
index 0000000..b4f0de4
--- /dev/null
+++ b/third_party/spiro/ppedit/sexp.c
@@ -0,0 +1,127 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+#include <stdio.h>
+#include <math.h>
+
+#include "sexp.h"
+
+/* This is handcoded to avoid locale problems. */
+static int
+parse_double(sexp_reader *sr)
+{
+ double sign = 1.0, val = 0.0;
+ int i;
+ int numstart;
+ const char * const b = sr->tokbuf;
+ int is_double = 1;
+
+ i = 0;
+ if (b[i] == '+') {
+ i++;
+ } else if (b[i] == '-') {
+ sign = -1.0;
+ i++;
+ }
+ numstart = i;
+ while (b[i] >= '0' && b[i] <= '9')
+ val = val * 10.0 + b[i++] - '0';
+ if (b[i] == '.') {
+ double frac = 1.0;
+
+ for (i++; b[i] >= '0' && b[i] <= '9'; i++) {
+ frac *= 0.1;
+ val += (b[i] - '0') * frac;
+ }
+
+ /* A '.' without any digits on either side isn't valid. */
+ if (i == numstart + 1)
+ is_double = 0;
+ }
+ if (b[i] == 'e' || b[i] == 'E') {
+ int expsign = 1, exp = 0;
+ int expstart;
+
+ if (b[i] == '+') {
+ i++;
+ } else if (b[i] == '-') {
+ expsign = -1;
+ i++;
+ }
+ expstart = i;
+ while (b[i] >= '0' && b[i] <= '9')
+ exp = exp * 10 + b[i++] - '0';
+
+ if (i == expstart)
+ is_double = 0;
+ val *= pow(10.0, expsign * exp);
+ }
+ val *= sign;
+ sr->d = val;
+ if (b[i] != 0) is_double = 0;
+ sr->is_double = is_double;
+ return is_double;
+}
+
+/* Return values: 0 = EOF, 1 = token but not double, 2 = valid double */
+int
+sexp_token(sexp_reader *sr)
+{
+ int c;
+ int i;
+
+ sr->singlechar = -1;
+ if (sr->f == NULL)
+ return 0;
+
+ for (;;) {
+ c = getc(sr->f);
+ if (c == EOF) {
+ sr->f = NULL;
+ return 0;
+ } else if (c == '#') {
+ do {
+ c = getc(sr->f);
+ } while (c != EOF && c != '\r' && c != '\n');
+ } else if (c != ' ' && c != '\r' && c != '\n' && c != '\t')
+ break;
+ }
+ sr->tokbuf[0] = c;
+ i = 1;
+ if (c != '(' && c != ')') {
+ for (;;) {
+ c = getc(sr->f);
+ if (c == EOF) {
+ sr->f = NULL;
+ break;
+ } else if (c == ' ' || c == '\r' || c == '\n' || c == '\t') {
+ break;
+ } else if (c == '(' || c == ')' || c == '#') {
+ ungetc(c, sr->f);
+ break;
+ } else if (i < sizeof(sr->tokbuf) - 1)
+ sr->tokbuf[i++] = c;
+ }
+ }
+ sr->tokbuf[i] = 0;
+ if (i == 1)
+ sr->singlechar = sr->tokbuf[0];
+ return 1 + parse_double(sr);
+}
diff --git a/third_party/spiro/ppedit/sexp.h b/third_party/spiro/ppedit/sexp.h
new file mode 100644
index 0000000..162152d
--- /dev/null
+++ b/third_party/spiro/ppedit/sexp.h
@@ -0,0 +1,10 @@
+typedef struct {
+ FILE *f;
+ char tokbuf[256];
+ int singlechar;
+ int is_double;
+ double d;
+} sexp_reader;
+
+int
+sexp_token(sexp_reader *sr);
diff --git a/third_party/spiro/ppedit/spiro.c b/third_party/spiro/ppedit/spiro.c
new file mode 100644
index 0000000..551697c
--- /dev/null
+++ b/third_party/spiro/ppedit/spiro.c
@@ -0,0 +1,1070 @@
+/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+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.
+
+*/
+/* C implementation of third-order polynomial spirals. */
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bezctx_intf.h"
+#include "spiro.h"
+
+struct spiro_seg_s {
+ double x;
+ double y;
+ char ty;
+ double bend_th;
+ double ks[4];
+ double seg_ch;
+ double seg_th;
+ double l;
+};
+
+typedef struct {
+ double a[11]; /* band-diagonal matrix */
+ double al[5]; /* lower part of band-diagonal decomposition */
+} bandmat;
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846 /* pi */
+#endif
+
+int n = 4;
+
+#ifndef ORDER
+#define ORDER 12
+#endif
+
+/* Integrate polynomial spiral curve over range -.5 .. .5. */
+void
+integrate_spiro(const double ks[4], double xy[2])
+{
+#if 0
+ int n = 1024;
+#endif
+ double th1 = ks[0];
+ double th2 = .5 * ks[1];
+ double th3 = (1./6) * ks[2];
+ double th4 = (1./24) * ks[3];
+ double x, y;
+ double ds = 1. / n;
+ double ds2 = ds * ds;
+ double ds3 = ds2 * ds;
+ double k0 = ks[0] * ds;
+ double k1 = ks[1] * ds;
+ double k2 = ks[2] * ds;
+ double k3 = ks[3] * ds;
+ int i;
+ double s = .5 * ds - .5;
+
+ x = 0;
+ y = 0;
+
+ for (i = 0; i < n; i++) {
+
+#if ORDER > 2
+ double u, v;
+ double km0, km1, km2, km3;
+
+ if (n == 1) {
+ km0 = k0;
+ km1 = k1 * ds;
+ km2 = k2 * ds2;
+ } else {
+ km0 = (((1./6) * k3 * s + .5 * k2) * s + k1) * s + k0;
+ km1 = ((.5 * k3 * s + k2) * s + k1) * ds;
+ km2 = (k3 * s + k2) * ds2;
+ }
+ km3 = k3 * ds3;
+#endif
+
+ {
+
+#if ORDER == 4
+ double km0_2 = km0 * km0;
+ u = 24 - km0_2;
+ v = km1;
+#endif
+
+#if ORDER == 6
+ double km0_2 = km0 * km0;
+ double km0_4 = km0_2 * km0_2;
+ u = 24 - km0_2 + (km0_4 - 4 * km0 * km2 - 3 * km1 * km1) * (1./80);
+ v = km1 + (km3 - 6 * km0_2 * km1) * (1./80);
+#endif
+
+#if ORDER == 8
+ double t1_1 = km0;
+ double t1_2 = .5 * km1;
+ double t1_3 = (1./6) * km2;
+ double t1_4 = (1./24) * km3;
+ double t2_2 = t1_1 * t1_1;
+ double t2_3 = 2 * (t1_1 * t1_2);
+ double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2;
+ double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3);
+ double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3;
+ double t3_4 = t2_2 * t1_2 + t2_3 * t1_1;
+ double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1;
+ double t4_4 = t2_2 * t2_2;
+ double t4_5 = 2 * (t2_2 * t2_3);
+ double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3;
+ double t5_6 = t4_4 * t1_2 + t4_5 * t1_1;
+ double t6_6 = t4_4 * t2_2;
+ u = 1;
+ v = 0;
+ v += (1./12) * t1_2 + (1./80) * t1_4;
+ u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6;
+ v -= (1./480) * t3_4 + (1./2688) * t3_6;
+ u += (1./1920) * t4_4 + (1./10752) * t4_6;
+ v += (1./53760) * t5_6;
+ u -= (1./322560) * t6_6;
+#endif
+
+#if ORDER == 10
+ double t1_1 = km0;
+ double t1_2 = .5 * km1;
+ double t1_3 = (1./6) * km2;
+ double t1_4 = (1./24) * km3;
+ double t2_2 = t1_1 * t1_1;
+ double t2_3 = 2 * (t1_1 * t1_2);
+ double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2;
+ double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3);
+ double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3;
+ double t2_7 = 2 * (t1_3 * t1_4);
+ double t2_8 = t1_4 * t1_4;
+ double t3_4 = t2_2 * t1_2 + t2_3 * t1_1;
+ double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1;
+ double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1;
+ double t4_4 = t2_2 * t2_2;
+ double t4_5 = 2 * (t2_2 * t2_3);
+ double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3;
+ double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4);
+ double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4;
+ double t5_6 = t4_4 * t1_2 + t4_5 * t1_1;
+ double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1;
+ double t6_6 = t4_4 * t2_2;
+ double t6_7 = t4_4 * t2_3 + t4_5 * t2_2;
+ double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2;
+ double t7_8 = t6_6 * t1_2 + t6_7 * t1_1;
+ double t8_8 = t6_6 * t2_2;
+ u = 1;
+ v = 0;
+ v += (1./12) * t1_2 + (1./80) * t1_4;
+ u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8;
+ v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8;
+ u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8;
+ v += (1./53760) * t5_6 + (1./276480) * t5_8;
+ u -= (1./322560) * t6_6 + (1./1.65888e+06) * t6_8;
+ v -= (1./1.16122e+07) * t7_8;
+ u += (1./9.28973e+07) * t8_8;
+#endif
+
+#if ORDER == 12
+ double t1_1 = km0;
+ double t1_2 = .5 * km1;
+ double t1_3 = (1./6) * km2;
+ double t1_4 = (1./24) * km3;
+ double t2_2 = t1_1 * t1_1;
+ double t2_3 = 2 * (t1_1 * t1_2);
+ double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2;
+ double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3);
+ double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3;
+ double t2_7 = 2 * (t1_3 * t1_4);
+ double t2_8 = t1_4 * t1_4;
+ double t3_4 = t2_2 * t1_2 + t2_3 * t1_1;
+ double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1;
+ double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1;
+ double t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2;
+ double t4_4 = t2_2 * t2_2;
+ double t4_5 = 2 * (t2_2 * t2_3);
+ double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3;
+ double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4);
+ double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4;
+ double t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5);
+ double t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5;
+ double t5_6 = t4_4 * t1_2 + t4_5 * t1_1;
+ double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1;
+ double t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1;
+ double t6_6 = t4_4 * t2_2;
+ double t6_7 = t4_4 * t2_3 + t4_5 * t2_2;
+ double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2;
+ double t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2;
+ double t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2;
+ double t7_8 = t6_6 * t1_2 + t6_7 * t1_1;
+ double t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1;
+ double t8_8 = t6_6 * t2_2;
+ double t8_9 = t6_6 * t2_3 + t6_7 * t2_2;
+ double t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2;
+ double t9_10 = t8_8 * t1_2 + t8_9 * t1_1;
+ double t10_10 = t8_8 * t2_2;
+ u = 1;
+ v = 0;
+ v += (1./12) * t1_2 + (1./80) * t1_4;
+ u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8;
+ v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8 + (1./67584) * t3_10;
+ u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8 + (1./270336) * t4_10;
+ v += (1./53760) * t5_6 + (1./276480) * t5_8 + (1./1.35168e+06) * t5_10;
+ u -= (1./322560) * t6_6 + (1./1.65888e+06) * t6_8 + (1./8.11008e+06) * t6_10;
+ v -= (1./1.16122e+07) * t7_8 + (1./5.67706e+07) * t7_10;
+ u += (1./9.28973e+07) * t8_8 + (1./4.54164e+08) * t8_10;
+ v += (1./4.08748e+09) * t9_10;
+ u -= (1./4.08748e+10) * t10_10;
+#endif
+
+#if ORDER == 14
+ double t1_1 = km0;
+ double t1_2 = .5 * km1;
+ double t1_3 = (1./6) * km2;
+ double t1_4 = (1./24) * km3;
+ double t2_2 = t1_1 * t1_1;
+ double t2_3 = 2 * (t1_1 * t1_2);
+ double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2;
+ double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3);
+ double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3;
+ double t2_7 = 2 * (t1_3 * t1_4);
+ double t2_8 = t1_4 * t1_4;
+ double t3_4 = t2_2 * t1_2 + t2_3 * t1_1;
+ double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1;
+ double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1;
+ double t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2;
+ double t3_12 = t2_8 * t1_4;
+ double t4_4 = t2_2 * t2_2;
+ double t4_5 = 2 * (t2_2 * t2_3);
+ double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3;
+ double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4);
+ double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4;
+ double t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5);
+ double t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5;
+ double t4_11 = 2 * (t2_3 * t2_8 + t2_4 * t2_7 + t2_5 * t2_6);
+ double t4_12 = 2 * (t2_4 * t2_8 + t2_5 * t2_7) + t2_6 * t2_6;
+ double t5_6 = t4_4 * t1_2 + t4_5 * t1_1;
+ double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1;
+ double t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1;
+ double t5_12 = t4_8 * t1_4 + t4_9 * t1_3 + t4_10 * t1_2 + t4_11 * t1_1;
+ double t6_6 = t4_4 * t2_2;
+ double t6_7 = t4_4 * t2_3 + t4_5 * t2_2;
+ double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2;
+ double t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2;
+ double t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2;
+ double t6_11 = t4_4 * t2_7 + t4_5 * t2_6 + t4_6 * t2_5 + t4_7 * t2_4 + t4_8 * t2_3 + t4_9 * t2_2;
+ double t6_12 = t4_4 * t2_8 + t4_5 * t2_7 + t4_6 * t2_6 + t4_7 * t2_5 + t4_8 * t2_4 + t4_9 * t2_3 + t4_10 * t2_2;
+ double t7_8 = t6_6 * t1_2 + t6_7 * t1_1;
+ double t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1;
+ double t7_12 = t6_8 * t1_4 + t6_9 * t1_3 + t6_10 * t1_2 + t6_11 * t1_1;
+ double t8_8 = t6_6 * t2_2;
+ double t8_9 = t6_6 * t2_3 + t6_7 * t2_2;
+ double t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2;
+ double t8_11 = t6_6 * t2_5 + t6_7 * t2_4 + t6_8 * t2_3 + t6_9 * t2_2;
+ double t8_12 = t6_6 * t2_6 + t6_7 * t2_5 + t6_8 * t2_4 + t6_9 * t2_3 + t6_10 * t2_2;
+ double t9_10 = t8_8 * t1_2 + t8_9 * t1_1;
+ double t9_12 = t8_8 * t1_4 + t8_9 * t1_3 + t8_10 * t1_2 + t8_11 * t1_1;
+ double t10_10 = t8_8 * t2_2;
+ double t10_11 = t8_8 * t2_3 + t8_9 * t2_2;
+ double t10_12 = t8_8 * t2_4 + t8_9 * t2_3 + t8_10 * t2_2;
+ double t11_12 = t10_10 * t1_2 + t10_11 * t1_1;
+ double t12_12 = t10_10 * t2_2;
+ u = 1;
+ v = 0;
+ v += (1./12) * t1_2 + (1./80) * t1_4;
+ u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8;
+ v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8 + (1./67584) * t3_10 + (1./319488) * t3_12;
+ u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8 + (1./270336) * t4_10 + (1./1.27795e+06) * t4_12;
+ v += (1./53760) * t5_6 + (1./276480) * t5_8 + (1./1.35168e+06) * t5_10 + (1./6.38976e+06) * t5_12;
+ u -= (1./322560) * t6_6 + (1./1.65888e+06) * t6_8 + (1./8.11008e+06) * t6_10 + (1./3.83386e+07) * t6_12;
+ v -= (1./1.16122e+07) * t7_8 + (1./5.67706e+07) * t7_10 + (1./2.6837e+08) * t7_12;
+ u += (1./9.28973e+07) * t8_8 + (1./4.54164e+08) * t8_10 + (1./2.14696e+09) * t8_12;
+ v += (1./4.08748e+09) * t9_10 + (1./1.93226e+10) * t9_12;
+ u -= (1./4.08748e+10) * t10_10 + (1./1.93226e+11) * t10_12;
+ v -= (1./2.12549e+12) * t11_12;
+ u += (1./2.55059e+13) * t12_12;
+#endif
+
+#if ORDER == 16
+ double t1_1 = km0;
+ double t1_2 = .5 * km1;
+ double t1_3 = (1./6) * km2;
+ double t1_4 = (1./24) * km3;
+ double t2_2 = t1_1 * t1_1;
+ double t2_3 = 2 * (t1_1 * t1_2);
+ double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2;
+ double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3);
+ double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3;
+ double t2_7 = 2 * (t1_3 * t1_4);
+ double t2_8 = t1_4 * t1_4;
+ double t3_4 = t2_2 * t1_2 + t2_3 * t1_1;
+ double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1;
+ double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1;
+ double t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2;
+ double t3_12 = t2_8 * t1_4;
+ double t4_4 = t2_2 * t2_2;
+ double t4_5 = 2 * (t2_2 * t2_3);
+ double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3;
+ double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4);
+ double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4;
+ double t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5);
+ double t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5;
+ double t4_11 = 2 * (t2_3 * t2_8 + t2_4 * t2_7 + t2_5 * t2_6);
+ double t4_12 = 2 * (t2_4 * t2_8 + t2_5 * t2_7) + t2_6 * t2_6;
+ double t4_13 = 2 * (t2_5 * t2_8 + t2_6 * t2_7);
+ double t4_14 = 2 * (t2_6 * t2_8) + t2_7 * t2_7;
+ double t5_6 = t4_4 * t1_2 + t4_5 * t1_1;
+ double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1;
+ double t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1;
+ double t5_12 = t4_8 * t1_4 + t4_9 * t1_3 + t4_10 * t1_2 + t4_11 * t1_1;
+ double t5_14 = t4_10 * t1_4 + t4_11 * t1_3 + t4_12 * t1_2 + t4_13 * t1_1;
+ double t6_6 = t4_4 * t2_2;
+ double t6_7 = t4_4 * t2_3 + t4_5 * t2_2;
+ double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2;
+ double t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2;
+ double t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2;
+ double t6_11 = t4_4 * t2_7 + t4_5 * t2_6 + t4_6 * t2_5 + t4_7 * t2_4 + t4_8 * t2_3 + t4_9 * t2_2;
+ double t6_12 = t4_4 * t2_8 + t4_5 * t2_7 + t4_6 * t2_6 + t4_7 * t2_5 + t4_8 * t2_4 + t4_9 * t2_3 + t4_10 * t2_2;
+ double t6_13 = t4_5 * t2_8 + t4_6 * t2_7 + t4_7 * t2_6 + t4_8 * t2_5 + t4_9 * t2_4 + t4_10 * t2_3 + t4_11 * t2_2;
+ double t6_14 = t4_6 * t2_8 + t4_7 * t2_7 + t4_8 * t2_6 + t4_9 * t2_5 + t4_10 * t2_4 + t4_11 * t2_3 + t4_12 * t2_2;
+ double t7_8 = t6_6 * t1_2 + t6_7 * t1_1;
+ double t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1;
+ double t7_12 = t6_8 * t1_4 + t6_9 * t1_3 + t6_10 * t1_2 + t6_11 * t1_1;
+ double t7_14 = t6_10 * t1_4 + t6_11 * t1_3 + t6_12 * t1_2 + t6_13 * t1_1;
+ double t8_8 = t6_6 * t2_2;
+ double t8_9 = t6_6 * t2_3 + t6_7 * t2_2;
+ double t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2;
+ double t8_11 = t6_6 * t2_5 + t6_7 * t2_4 + t6_8 * t2_3 + t6_9 * t2_2;
+ double t8_12 = t6_6 * t2_6 + t6_7 * t2_5 + t6_8 * t2_4 + t6_9 * t2_3 + t6_10 * t2_2;
+ double t8_13 = t6_6 * t2_7 + t6_7 * t2_6 + t6_8 * t2_5 + t6_9 * t2_4 + t6_10 * t2_3 + t6_11 * t2_2;
+ double t8_14 = t6_6 * t2_8 + t6_7 * t2_7 + t6_8 * t2_6 + t6_9 * t2_5 + t6_10 * t2_4 + t6_11 * t2_3 + t6_12 * t2_2;
+ double t9_10 = t8_8 * t1_2 + t8_9 * t1_1;
+ double t9_12 = t8_8 * t1_4 + t8_9 * t1_3 + t8_10 * t1_2 + t8_11 * t1_1;
+ double t9_14 = t8_10 * t1_4 + t8_11 * t1_3 + t8_12 * t1_2 + t8_13 * t1_1;
+ double t10_10 = t8_8 * t2_2;
+ double t10_11 = t8_8 * t2_3 + t8_9 * t2_2;
+ double t10_12 = t8_8 * t2_4 + t8_9 * t2_3 + t8_10 * t2_2;
+ double t10_13 = t8_8 * t2_5 + t8_9 * t2_4 + t8_10 * t2_3 + t8_11 * t2_2;
+ double t10_14 = t8_8 * t2_6 + t8_9 * t2_5 + t8_10 * t2_4 + t8_11 * t2_3 + t8_12 * t2_2;
+ double t11_12 = t10_10 * t1_2 + t10_11 * t1_1;
+ double t11_14 = t10_10 * t1_4 + t10_11 * t1_3 + t10_12 * t1_2 + t10_13 * t1_1;
+ double t12_12 = t10_10 * t2_2;
+ double t12_13 = t10_10 * t2_3 + t10_11 * t2_2;
+ double t12_14 = t10_10 * t2_4 + t10_11 * t2_3 + t10_12 * t2_2;
+ double t13_14 = t12_12 * t1_2 + t12_13 * t1_1;
+ double t14_14 = t12_12 * t2_2;
+ u = 1;
+ u -= 1./24 * t2_2 + 1./160 * t2_4 + 1./896 * t2_6 + 1./4608 * t2_8;
+ u += 1./1920 * t4_4 + 1./10752 * t4_6 + 1./55296 * t4_8 + 1./270336 * t4_10 + 1./1277952 * t4_12 + 1./5898240 * t4_14;
+ u -= 1./322560 * t6_6 + 1./1658880 * t6_8 + 1./8110080 * t6_10 + 1./38338560 * t6_12 + 1./176947200 * t6_14;
+ u += 1./92897280 * t8_8 + 1./454164480 * t8_10 + 4.6577500191e-10 * t8_12 + 1.0091791708e-10 * t8_14;
+ u -= 2.4464949595e-11 * t10_10 + 5.1752777990e-12 * t10_12 + 1.1213101898e-12 * t10_14;
+ u += 3.9206649992e-14 * t12_12 + 8.4947741650e-15 * t12_14;
+ u -= 4.6674583324e-17 * t14_14;
+ v = 0;
+ v += 1./12 * t1_2 + 1./80 * t1_4;
+ v -= 1./480 * t3_4 + 1./2688 * t3_6 + 1./13824 * t3_8 + 1./67584 * t3_10 + 1./319488 * t3_12;
+ v += 1./53760 * t5_6 + 1./276480 * t5_8 + 1./1351680 * t5_10 + 1./6389760 * t5_12 + 1./29491200 * t5_14;
+ v -= 1./11612160 * t7_8 + 1./56770560 * t7_10 + 1./268369920 * t7_12 + 8.0734333664e-10 * t7_14;
+ v += 2.4464949595e-10 * t9_10 + 5.1752777990e-11 * t9_12 + 1.1213101898e-11 * t9_14;
+ v -= 4.7047979991e-13 * t11_12 + 1.0193728998e-13 * t11_14;
+ v += 6.5344416654e-16 * t13_14;
+#endif
+
+ }
+
+ if (n == 1) {
+#if ORDER == 2
+ x = 1;
+ y = 0;
+#else
+ x = u;
+ y = v;
+#endif
+ } else {
+ double th = (((th4 * s + th3) * s + th2) * s + th1) * s;
+ double cth = cos(th);
+ double sth = sin(th);
+
+#if ORDER == 2
+ x += cth;
+ y += sth;
+#else
+ x += cth * u - sth * v;
+ y += cth * v + sth * u;
+#endif
+ s += ds;
+ }
+ }
+
+#if ORDER == 4 || ORDER == 6
+ xy[0] = x * (1./24 * ds);
+ xy[1] = y * (1./24 * ds);
+#else
+ xy[0] = x * ds;
+ xy[1] = y * ds;
+#endif
+}
+
+static double
+compute_ends(const double ks[4], double ends[2][4], double seg_ch)
+{
+ double xy[2];
+ double ch, th;
+ double l, l2, l3;
+ double th_even, th_odd;
+ double k0_even, k0_odd;
+ double k1_even, k1_odd;
+ double k2_even, k2_odd;
+
+ integrate_spiro(ks, xy);
+ ch = hypot(xy[0], xy[1]);
+ th = atan2(xy[1], xy[0]);
+ l = ch / seg_ch;
+
+ th_even = .5 * ks[0] + (1./48) * ks[2];
+ th_odd = .125 * ks[1] + (1./384) * ks[3] - th;
+ ends[0][0] = th_even - th_odd;
+ ends[1][0] = th_even + th_odd;
+ k0_even = l * (ks[0] + .125 * ks[2]);
+ k0_odd = l * (.5 * ks[1] + (1./48) * ks[3]);
+ ends[0][1] = k0_even - k0_odd;
+ ends[1][1] = k0_even + k0_odd;
+ l2 = l * l;
+ k1_even = l2 * (ks[1] + .125 * ks[3]);
+ k1_odd = l2 * .5 * ks[2];
+ ends[0][2] = k1_even - k1_odd;
+ ends[1][2] = k1_even + k1_odd;
+ l3 = l2 * l;
+ k2_even = l3 * ks[2];
+ k2_odd = l3 * .5 * ks[3];
+ ends[0][3] = k2_even - k2_odd;
+ ends[1][3] = k2_even + k2_odd;
+
+ return l;
+}
+
+static void
+compute_pderivs(const spiro_seg *s, double ends[2][4], double derivs[4][2][4],
+ int jinc)
+{
+ double recip_d = 2e6;
+ double delta = 1./ recip_d;
+ double try_ks[4];
+ double try_ends[2][4];
+ int i, j, k;
+
+ compute_ends(s->ks, ends, s->seg_ch);
+ for (i = 0; i < jinc; i++) {
+ for (j = 0; j < 4; j++)
+ try_ks[j] = s->ks[j];
+ try_ks[i] += delta;
+ compute_ends(try_ks, try_ends, s->seg_ch);
+ for (k = 0; k < 2; k++)
+ for (j = 0; j < 4; j++)
+ derivs[j][k][i] = recip_d * (try_ends[k][j] - ends[k][j]);
+ }
+}
+
+static double
+mod_2pi(double th)
+{
+ double u = th / (2 * M_PI);
+ return 2 * M_PI * (u - floor(u + 0.5));
+}
+
+static spiro_seg *
+setup_path(const spiro_cp *src, int n)
+{
+ int n_seg = src[0].ty == '{' ? n - 1 : n;
+ spiro_seg *r = (spiro_seg *)malloc((n_seg + 1) * sizeof(spiro_seg));
+ int i;
+ int ilast;
+
+ for (i = 0; i < n_seg; i++) {
+ r[i].x = src[i].x;
+ r[i].y = src[i].y;
+ r[i].ty = src[i].ty;
+ r[i].ks[0] = 0.;
+ r[i].ks[1] = 0.;
+ r[i].ks[2] = 0.;
+ r[i].ks[3] = 0.;
+ }
+ r[n_seg].x = src[n_seg % n].x;
+ r[n_seg].y = src[n_seg % n].y;
+ r[n_seg].ty = src[n_seg % n].ty;
+
+ for (i = 0; i < n_seg; i++) {
+ double dx = r[i + 1].x - r[i].x;
+ double dy = r[i + 1].y - r[i].y;
+ r[i].seg_ch = hypot(dx, dy);
+ r[i].seg_th = atan2(dy, dx);
+ }
+
+ ilast = n_seg - 1;
+ for (i = 0; i < n_seg; i++) {
+ if (r[i].ty == '{' || r[i].ty == '}' || r[i].ty == 'v')
+ r[i].bend_th = 0.;
+ else
+ r[i].bend_th = mod_2pi(r[i].seg_th - r[ilast].seg_th);
+ ilast = i;
+ }
+ return r;
+}
+
+static void
+bandec11(bandmat *m, int *perm, int n)
+{
+ int i, j, k;
+ int l;
+
+ /* pack top triangle to the left. */
+ for (i = 0; i < 5; i++) {
+ for (j = 0; j < i + 6; j++)
+ m[i].a[j] = m[i].a[j + 5 - i];
+ for (; j < 11; j++)
+ m[i].a[j] = 0.;
+ }
+ l = 5;
+ for (k = 0; k < n; k++) {
+ int pivot = k;
+ double pivot_val = m[k].a[0];
+ double pivot_scale;
+
+ if (l < n) l++;
+
+ for (j = k + 1; j < l; j++)
+ if (fabs(m[j].a[0]) > fabs(pivot_val)) {
+ pivot_val = m[j].a[0];
+ pivot = j;
+ }
+
+ perm[k] = pivot;
+ if (pivot != k) {
+ for (j = 0; j < 11; j++) {
+ double tmp = m[k].a[j];
+ m[k].a[j] = m[pivot].a[j];
+ m[pivot].a[j] = tmp;
+ }
+ }
+
+ if (fabs(pivot_val) < 1e-12) pivot_val = 1e-12;
+ pivot_scale = 1. / pivot_val;
+ for (i = k + 1; i < l; i++) {
+ double x = m[i].a[0] * pivot_scale;
+ m[k].al[i - k - 1] = x;
+ for (j = 1; j < 11; j++)
+ m[i].a[j - 1] = m[i].a[j] - x * m[k].a[j];
+ m[i].a[10] = 0.;
+ }
+ }
+}
+
+static void
+banbks11(const bandmat *m, const int *perm, double *v, int n)
+{
+ int i, k, l;
+
+ /* forward substitution */
+ l = 5;
+ for (k = 0; k < n; k++) {
+ i = perm[k];
+ if (i != k) {
+ double tmp = v[k];
+ v[k] = v[i];
+ v[i] = tmp;
+ }
+ if (l < n) l++;
+ for (i = k + 1; i < l; i++)
+ v[i] -= m[k].al[i - k - 1] * v[k];
+ }
+
+ /* back substitution */
+ l = 1;
+ for (i = n - 1; i >= 0; i--) {
+ double x = v[i];
+ for (k = 1; k < l; k++)
+ x -= m[i].a[k] * v[k + i];
+ v[i] = x / m[i].a[0];
+ if (l < 11) l++;
+ }
+}
+
+int compute_jinc(char ty0, char ty1)
+{
+ if (ty0 == 'o' || ty1 == 'o' ||
+ ty0 == ']' || ty1 == '[')
+ return 4;
+ else if (ty0 == 'c' && ty1 == 'c')
+ return 2;
+ else if (((ty0 == '{' || ty0 == 'v' || ty0 == '[') && ty1 == 'c') ||
+ (ty0 == 'c' && (ty1 == '}' || ty1 == 'v' || ty1 == ']')))
+ return 1;
+ else
+ return 0;
+}
+
+int count_vec(const spiro_seg *s, int nseg)
+{
+ int i;
+ int n = 0;
+
+ for (i = 0; i < nseg; i++)
+ n += compute_jinc(s[i].ty, s[i + 1].ty);
+ return n;
+}
+
+static void
+add_mat_line(bandmat *m, double *v,
+ double derivs[4], double x, double y, int j, int jj, int jinc,
+ int nmat)
+{
+ int k;
+
+ if (jj >= 0) {
+ int joff = (j + 5 - jj + nmat) % nmat;
+ v[jj] += x;
+ for (k = 0; k < jinc; k++)
+ m[jj].a[joff + k] += y * derivs[k];
+ }
+}
+
+static double
+spiro_iter(spiro_seg *s, bandmat *m, int *perm, double *v, int n)
+{
+ int cyclic = s[0].ty != '{' && s[0].ty != 'v';
+ int i, j, jj;
+ int nmat = count_vec(s, n);
+ double norm;
+ int n_invert;
+
+ for (i = 0; i < nmat; i++) {
+ v[i] = 0.;
+ for (j = 0; j < 11; j++)
+ m[i].a[j] = 0.;
+ for (j = 0; j < 5; j++)
+ m[i].al[j] = 0.;
+ }
+
+ j = 0;
+ if (s[0].ty == 'o')
+ jj = nmat - 2;
+ else if (s[0].ty == 'c' || s[0].ty == '[' || s[0].ty == ']')
+ jj = nmat - 1;
+ else
+ jj = 0;
+ for (i = 0; i < n; i++) {
+ char ty0 = s[i].ty;
+ char ty1 = s[i + 1].ty;
+ int jinc = compute_jinc(ty0, ty1);
+ double th = s[i].bend_th;
+ double ends[2][4];
+ double derivs[4][2][4];
+ int jthl = -1, jk0l = -1, jk1l = -1, jk2l = -1;
+ int jthr = -1, jk0r = -1, jk1r = -1, jk2r = -1;
+
+ compute_pderivs(&s[i], ends, derivs, jinc);
+
+ /* constraints crossing left */
+ if (ty0 == 'o' || ty0 == 'c' || ty0 == '[' || ty0 == ']') {
+ jthl = jj++;
+ jj %= nmat;
+ jk0l = jj++;
+ }
+ if (ty0 == 'o') {
+ jj %= nmat;
+ jk1l = jj++;
+ jk2l = jj++;
+ }
+
+ /* constraints on left */
+ if ((ty0 == '[' || ty0 == 'v' || ty0 == '{' || ty0 == 'c') &&
+ jinc == 4) {
+ if (ty0 != 'c')
+ jk1l = jj++;
+ jk2l = jj++;
+ }
+
+ /* constraints on right */
+ if ((ty1 == ']' || ty1 == 'v' || ty1 == '}' || ty1 == 'c') &&
+ jinc == 4) {
+ if (ty1 != 'c')
+ jk1r = jj++;
+ jk2r = jj++;
+ }
+
+ /* constraints crossing right */
+ if (ty1 == 'o' || ty1 == 'c' || ty1 == '[' || ty1 == ']') {
+ jthr = jj;
+ jk0r = (jj + 1) % nmat;
+ }
+ if (ty1 == 'o') {
+ jk1r = (jj + 2) % nmat;
+ jk2r = (jj + 3) % nmat;
+ }
+
+ add_mat_line(m, v, derivs[0][0], th - ends[0][0], 1, j, jthl, jinc, nmat);
+ add_mat_line(m, v, derivs[1][0], ends[0][1], -1, j, jk0l, jinc, nmat);
+ add_mat_line(m, v, derivs[2][0], ends[0][2], -1, j, jk1l, jinc, nmat);
+ add_mat_line(m, v, derivs[3][0], ends[0][3], -1, j, jk2l, jinc, nmat);
+ add_mat_line(m, v, derivs[0][1], -ends[1][0], 1, j, jthr, jinc, nmat);
+ add_mat_line(m, v, derivs[1][1], -ends[1][1], 1, j, jk0r, jinc, nmat);
+ add_mat_line(m, v, derivs[2][1], -ends[1][2], 1, j, jk1r, jinc, nmat);
+ add_mat_line(m, v, derivs[3][1], -ends[1][3], 1, j, jk2r, jinc, nmat);
+ j += jinc;
+ }
+ if (cyclic) {
+ memcpy(m + nmat, m, sizeof(bandmat) * nmat);
+ memcpy(m + 2 * nmat, m, sizeof(bandmat) * nmat);
+ memcpy(v + nmat, v, sizeof(double) * nmat);
+ memcpy(v + 2 * nmat, v, sizeof(double) * nmat);
+ n_invert = 3 * nmat;
+ j = nmat;
+ } else {
+ n_invert = nmat;
+ j = 0;
+ }
+ bandec11(m, perm, n_invert);
+ banbks11(m, perm, v, n_invert);
+ norm = 0.;
+ for (i = 0; i < n; i++) {
+ char ty0 = s[i].ty;
+ char ty1 = s[i + 1].ty;
+ int jinc = compute_jinc(ty0, ty1);
+ int k;
+
+ for (k = 0; k < jinc; k++) {
+ double dk = v[j++];
+
+ s[i].ks[k] += dk;
+ norm += dk * dk;
+ }
+ }
+ return norm;
+}
+
+int
+solve_spiro(spiro_seg *s, int nseg)
+{
+ bandmat *m;
+ double *v;
+ int *perm;
+ int nmat = count_vec(s, nseg);
+ int n_alloc = nmat;
+ double norm;
+ int i;
+
+ if (nmat == 0)
+ return 0;
+ if (s[0].ty != '{' && s[0].ty != 'v')
+ n_alloc *= 3;
+ if (n_alloc < 5)
+ n_alloc = 5;
+ m = (bandmat *)malloc(sizeof(bandmat) * n_alloc);
+ v = (double *)malloc(sizeof(double) * n_alloc);
+ perm = (int *)malloc(sizeof(int) * n_alloc);
+
+ for (i = 0; i < 10; i++) {
+ norm = spiro_iter(s, m, perm, v, nseg);
+#ifdef VERBOSE
+ printf("%% norm = %g\n", norm);
+#endif
+ if (norm < 1e-12) break;
+ }
+
+ free(m);
+ free(v);
+ free(perm);
+ return 0;
+}
+
+static void
+spiro_seg_to_bpath(const double ks[4],
+ double x0, double y0, double x1, double y1,
+ bezctx *bc, int depth)
+{
+ double bend = fabs(ks[0]) + fabs(.5 * ks[1]) + fabs(.125 * ks[2]) +
+ fabs((1./48) * ks[3]);
+
+ if (!bend > 1e-8) {
+ bezctx_lineto(bc, x1, y1);
+ } else {
+ double seg_ch = hypot(x1 - x0, y1 - y0);
+ double seg_th = atan2(y1 - y0, x1 - x0);
+ double xy[2];
+ double ch, th;
+ double scale, rot;
+ double th_even, th_odd;
+ double ul, vl;
+ double ur, vr;
+
+ integrate_spiro(ks, xy);
+ ch = hypot(xy[0], xy[1]);
+ th = atan2(xy[1], xy[0]);
+ scale = seg_ch / ch;
+ rot = seg_th - th;
+ if (depth > 5 || bend < 1.) {
+ th_even = (1./384) * ks[3] + (1./8) * ks[1] + rot;
+ th_odd = (1./48) * ks[2] + .5 * ks[0];
+ ul = (scale * (1./3)) * cos(th_even - th_odd);
+ vl = (scale * (1./3)) * sin(th_even - th_odd);
+ ur = (scale * (1./3)) * cos(th_even + th_odd);
+ vr = (scale * (1./3)) * sin(th_even + th_odd);
+ bezctx_curveto(bc, x0 + ul, y0 + vl, x1 - ur, y1 - vr, x1, y1);
+ } else {
+ /* subdivide */
+ double ksub[4];
+ double thsub;
+ double xysub[2];
+ double xmid, ymid;
+ double cth, sth;
+
+ ksub[0] = .5 * ks[0] - .125 * ks[1] + (1./64) * ks[2] - (1./768) * ks[3];
+ ksub[1] = .25 * ks[1] - (1./16) * ks[2] + (1./128) * ks[3];
+ ksub[2] = .125 * ks[2] - (1./32) * ks[3];
+ ksub[3] = (1./16) * ks[3];
+ thsub = rot - .25 * ks[0] + (1./32) * ks[1] - (1./384) * ks[2] + (1./6144) * ks[3];
+ cth = .5 * scale * cos(thsub);
+ sth = .5 * scale * sin(thsub);
+ integrate_spiro(ksub, xysub);
+ xmid = x0 + cth * xysub[0] - sth * xysub[1];
+ ymid = y0 + cth * xysub[1] + sth * xysub[0];
+ spiro_seg_to_bpath(ksub, x0, y0, xmid, ymid, bc, depth + 1);
+ ksub[0] += .25 * ks[1] + (1./384) * ks[3];
+ ksub[1] += .125 * ks[2];
+ ksub[2] += (1./16) * ks[3];
+ spiro_seg_to_bpath(ksub, xmid, ymid, x1, y1, bc, depth + 1);
+ }
+ }
+}
+
+spiro_seg *
+run_spiro(const spiro_cp *src, int n)
+{
+ int nseg = src[0].ty == '{' ? n - 1 : n;
+ spiro_seg *s = setup_path(src, n);
+ if (nseg > 1)
+ solve_spiro(s, nseg);
+ return s;
+}
+
+void
+free_spiro(spiro_seg *s)
+{
+ free(s);
+}
+
+void
+spiro_to_bpath(const spiro_seg *s, int n, bezctx *bc)
+{
+ int i;
+ int nsegs = s[n - 1].ty == '}' ? n - 1 : n;
+
+ for (i = 0; i < nsegs; i++) {
+ double x0 = s[i].x;
+ double y0 = s[i].y;
+ double x1 = s[i + 1].x;
+ double y1 = s[i + 1].y;
+
+ if (i == 0)
+ bezctx_moveto(bc, x0, y0, s[0].ty == '{');
+ bezctx_mark_knot(bc, i);
+ spiro_seg_to_bpath(s[i].ks, x0, y0, x1, y1, bc, 0);
+ }
+}
+
+double
+get_knot_th(const spiro_seg *s, int i)
+{
+ double ends[2][4];
+
+ if (i == 0) {
+ compute_ends(s[i].ks, ends, s[i].seg_ch);
+ return s[i].seg_th - ends[0][0];
+ } else {
+ compute_ends(s[i - 1].ks, ends, s[i - 1].seg_ch);
+ return s[i - 1].seg_th + ends[1][0];
+ }
+}
+
+#ifdef UNIT_TEST
+#include <stdio.h>
+#include <sys/time.h> /* for gettimeofday */
+
+static double
+get_time (void)
+{
+ struct timeval tv;
+ struct timezone tz;
+
+ gettimeofday (&tv, &tz);
+
+ return tv.tv_sec + 1e-6 * tv.tv_usec;
+}
+
+int
+test_integ(void) {
+ double ks[] = {1, 2, 3, 4};
+ double xy[2];
+ double xynom[2];
+ double ch, th;
+ int i, j;
+ int nsubdiv;
+
+ n = ORDER < 6 ? 4096 : 1024;
+ integrate_spiro(ks, xynom);
+ nsubdiv = ORDER < 12 ? 8 : 7;
+ for (i = 0; i < nsubdiv; i++) {
+ double st, en;
+ double err;
+ int n_iter = (1 << (20 - i));
+
+ n = 1 << i;
+ st = get_time();
+ for (j = 0; j < n_iter; j++)
+ integrate_spiro(ks, xy);
+ en = get_time();
+ err = hypot(xy[0] - xynom[0], xy[1] - xynom[1]);
+ printf("%d %d %g %g\n", ORDER, n, (en - st) / n_iter, err);
+ ch = hypot(xy[0], xy[1]);
+ th = atan2(xy[1], xy[0]);
+#if 0
+ printf("n = %d: integ(%g %g %g %g) = %g %g, ch = %g, th = %g\n", n,
+ ks[0], ks[1], ks[2], ks[3], xy[0], xy[1], ch, th);
+ printf("%d: %g %g\n", n, xy[0] - xynom[0], xy[1] - xynom[1]);
+#endif
+ }
+ return 0;
+}
+
+void
+print_seg(const double ks[4], double x0, double y0, double x1, double y1)
+{
+ double bend = fabs(ks[0]) + fabs(.5 * ks[1]) + fabs(.125 * ks[2]) +
+ fabs((1./48) * ks[3]);
+
+ if (bend < 1e-8) {
+ printf("%g %g lineto\n", x1, y1);
+ } else {
+ double seg_ch = hypot(x1 - x0, y1 - y0);
+ double seg_th = atan2(y1 - y0, x1 - x0);
+ double xy[2];
+ double ch, th;
+ double scale, rot;
+ double th_even, th_odd;
+ double ul, vl;
+ double ur, vr;
+
+ integrate_spiro(ks, xy);
+ ch = hypot(xy[0], xy[1]);
+ th = atan2(xy[1], xy[0]);
+ scale = seg_ch / ch;
+ rot = seg_th - th;
+ if (bend < 1.) {
+ th_even = (1./384) * ks[3] + (1./8) * ks[1] + rot;
+ th_odd = (1./48) * ks[2] + .5 * ks[0];
+ ul = (scale * (1./3)) * cos(th_even - th_odd);
+ vl = (scale * (1./3)) * sin(th_even - th_odd);
+ ur = (scale * (1./3)) * cos(th_even + th_odd);
+ vr = (scale * (1./3)) * sin(th_even + th_odd);
+ printf("%g %g %g %g %g %g curveto\n",
+ x0 + ul, y0 + vl, x1 - ur, y1 - vr, x1, y1);
+
+ } else {
+ /* subdivide */
+ double ksub[4];
+ double thsub;
+ double xysub[2];
+ double xmid, ymid;
+ double cth, sth;
+
+ ksub[0] = .5 * ks[0] - .125 * ks[1] + (1./64) * ks[2] - (1./768) * ks[3];
+ ksub[1] = .25 * ks[1] - (1./16) * ks[2] + (1./128) * ks[3];
+ ksub[2] = .125 * ks[2] - (1./32) * ks[3];
+ ksub[3] = (1./16) * ks[3];
+ thsub = rot - .25 * ks[0] + (1./32) * ks[1] - (1./384) * ks[2] + (1./6144) * ks[3];
+ cth = .5 * scale * cos(thsub);
+ sth = .5 * scale * sin(thsub);
+ integrate_spiro(ksub, xysub);
+ xmid = x0 + cth * xysub[0] - sth * xysub[1];
+ ymid = y0 + cth * xysub[1] + sth * xysub[0];
+ print_seg(ksub, x0, y0, xmid, ymid);
+ ksub[0] += .25 * ks[1] + (1./384) * ks[3];
+ ksub[1] += .125 * ks[2];
+ ksub[2] += (1./16) * ks[3];
+ print_seg(ksub, xmid, ymid, x1, y1);
+ }
+ }
+}
+
+void
+print_segs(const spiro_seg *segs, int nsegs)
+{
+ int i;
+
+ for (i = 0; i < nsegs; i++) {
+ double x0 = segs[i].x;
+ double y0 = segs[i].y;
+ double x1 = segs[i + 1].x;
+ double y1 = segs[i + 1].y;
+
+ if (i == 0)
+ printf("%g %g moveto\n", x0, y0);
+ printf("%% ks = [ %g %g %g %g ]\n",
+ segs[i].ks[0], segs[i].ks[1], segs[i].ks[2], segs[i].ks[3]);
+ print_seg(segs[i].ks, x0, y0, x1, y1);
+ }
+ printf("stroke\n");
+}
+
+int
+test_curve(void)
+{
+ spiro_cp path[] = {
+ {334, 117, 'v'},
+ {305, 176, 'v'},
+ {212, 142, 'c'},
+ {159, 171, 'c'},
+ {224, 237, 'c'},
+ {347, 335, 'c'},
+ {202, 467, 'c'},
+ {81, 429, 'v'},
+ {114, 368, 'v'},
+ {201, 402, 'c'},
+ {276, 369, 'c'},
+ {218, 308, 'c'},
+ {91, 211, 'c'},
+ {124, 111, 'c'},
+ {229, 82, 'c'}
+ };
+ spiro_seg *segs;
+ int i;
+
+ n = 1;
+ for (i = 0; i < 1000; i++) {
+ segs = setup_path(path, 15);
+ solve_spiro(segs, 15);
+ }
+ printf("100 800 translate 1 -1 scale 1 setlinewidth\n");
+ print_segs(segs, 15);
+ printf("showpage\n");
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ return test_curve();
+}
+#endif
diff --git a/third_party/spiro/ppedit/spiro.h b/third_party/spiro/ppedit/spiro.h
new file mode 100644
index 0000000..54de404
--- /dev/null
+++ b/third_party/spiro/ppedit/spiro.h
@@ -0,0 +1,18 @@
+typedef struct {
+ double x;
+ double y;
+ char ty;
+} spiro_cp;
+
+typedef struct spiro_seg_s spiro_seg;
+
+spiro_seg *
+run_spiro(const spiro_cp *src, int n);
+
+void
+free_spiro(spiro_seg *s);
+
+void
+spiro_to_bpath(const spiro_seg *s, int n, bezctx *bc);
+
+double get_knot_th(const spiro_seg *s, int i);
diff --git a/third_party/spiro/ppedit/zmisc.h b/third_party/spiro/ppedit/zmisc.h
new file mode 100644
index 0000000..ce3d4d3
--- /dev/null
+++ b/third_party/spiro/ppedit/zmisc.h
@@ -0,0 +1,12 @@
+/**
+ * Misc portability and convenience macros.
+ **/
+
+#include <stdlib.h>
+
+#define zalloc malloc
+#define zrealloc realloc
+#define zfree free
+
+#define znew(type, n) (type *)zalloc(sizeof(type) * (n))
+#define zrenew(type, p, n) (type *)zrealloc((p), sizeof(type) * (n))
diff --git a/third_party/spiro/x3/Makefile b/third_party/spiro/x3/Makefile
new file mode 100644
index 0000000..392417b
--- /dev/null
+++ b/third_party/spiro/x3/Makefile
@@ -0,0 +1,25 @@
+TARGET = gtk
+
+ifeq ($(TARGET),gtk)
+ X3_PLAT = X3_GTK
+ X3_INCL = `pkg-config --cflags gtk+-2.0`
+ X3_LIBS = `pkg-config --libs gtk+-2.0`
+endif
+
+ifeq ($(TARGET),carbon)
+ X3_PLAT = X3_CARBON
+ X3_LIBS = -framework Carbon
+endif
+
+ifeq ($(TARGET),win32)
+ X3_PLAT = X3_WIN32
+ X3_LIBS = -lgdi32
+endif
+
+CFLAGS = -g -Wall -D$(X3_PLAT) $(X3_INCL)
+LDFLAGS = -g
+LDLIBS = $(X3_LIBS)
+
+test: test.o x3$(TARGET).o x3common.o
+
+x3lisp.o: x3lisp.c
diff --git a/third_party/spiro/x3/pyrex/Makefile b/third_party/spiro/x3/pyrex/Makefile
new file mode 100644
index 0000000..d0324f3
--- /dev/null
+++ b/third_party/spiro/x3/pyrex/Makefile
@@ -0,0 +1,39 @@
+TARGET = gtk
+
+ifeq ($(TARGET),gtk)
+ X3_PLAT = X3_GTK
+ X3_INCL = `pkg-config --cflags gtk+-2.0`
+ X3_LIBS = `pkg-config --libs gtk+-2.0`
+ SHARED_FLAG = -shared
+endif
+
+ifeq ($(TARGET),carbon)
+ X3_PLAT = X3_CARBON
+ X3_LIBS = -framework Carbon
+ SHARED_FLAG = -dynamiclib -flat_namespace -undefined suppress
+endif
+
+ifeq ($(TARGET),win32)
+ X3_PLAT = X3_WIN32
+ X3_LIBS = -lgdi32
+endif
+
+PY_INCL := -I$(shell python -c "import distutils.sysconfig; print distutils.sysconfig.get_python_inc()")
+
+CFLAGS = -g -Wall -fPIC -I.. $(PY_INCL) -D$(X3_PLAT) $(X3_INCL)
+LDFLAGS = -g
+LDLIBS = $(X3_LIBS)
+
+x3.so: x3.o x3$(TARGET).o x3common.o
+ gcc $(SHARED_FLAG) $^ $(X3_LIBS) -o $@
+
+x3.c: x3.pyx
+ python2.4-pyrexc $<
+
+x3$(TARGET).o: ../x3$(TARGET).c
+ $(CC) -c $(CFLAGS) -o $@ $<
+
+x3common.o: ../x3common.c
+ $(CC) -c $(CFLAGS) -o $@ $<
+
+
diff --git a/third_party/spiro/x3/pyrex/beztest.py b/third_party/spiro/x3/pyrex/beztest.py
new file mode 100644
index 0000000..effd0d5
--- /dev/null
+++ b/third_party/spiro/x3/pyrex/beztest.py
@@ -0,0 +1,44 @@
+import x3
+from math import *
+
+def my_callback(cmd, what, arg, more):
+ print cmd, what, arg
+
+class bez:
+ def __init__(self):
+ self.coords = [(10, 10), (200, 10), (300, 200), (400, 100)]
+ self.hit = None
+ def draw(self, dc):
+ coords = self.coords
+ dc.setrgba(0, 0, 0.5, 1)
+ dc.moveto(coords[0][0], coords[0][1])
+ dc.curveto(coords[1][0], coords[1][1], coords[2][0], coords[2][1],
+ coords[3][0], coords[3][1])
+ dc.stroke()
+ dc.setrgba(0, 0.5, 0, 0.5)
+ dc.moveto(coords[0][0], coords[0][1])
+ dc.lineto(coords[1][0], coords[1][1])
+ dc.stroke()
+ dc.moveto(coords[2][0], coords[2][1])
+ dc.lineto(coords[3][0], coords[3][1])
+ dc.stroke()
+ dc.setrgba(1, 0, 0, .5)
+ for x, y in coords:
+ dc.rectangle(x - 3, y - 3, 6, 6)
+ dc.fill()
+ def mouse(self, button, mods, x, y):
+ if button == 1:
+ for i in range(4):
+ if hypot(x - self.coords[i][0], y - self.coords[i][1]) < 4:
+ self.hit = i
+ elif button == -1:
+ self.hit = None
+ elif self.hit != None:
+ self.coords[self.hit] = (x, y)
+ self.view.dirty()
+
+win = x3.window(0, "beztest", my_callback)
+
+x3.view(win, 259, bez())
+
+x3.main()
diff --git a/third_party/spiro/x3/pyrex/main.py b/third_party/spiro/x3/pyrex/main.py
new file mode 100644
index 0000000..e784f46
--- /dev/null
+++ b/third_party/spiro/x3/pyrex/main.py
@@ -0,0 +1,40 @@
+import x3
+
+def my_callback(cmd, what, arg, more):
+ print cmd, what, arg
+
+class my_viewclient:
+ def key(self, name, mods, code):
+ print name, mods, code
+ return 1
+ def mouse(self, buttons, mods, x, y):
+ print buttons, mods, x, y
+ def draw(self, dc):
+ print 'rect:', dc.rect
+ dc.moveto(0, 0)
+ dc.lineto(100, 100)
+ print dc.currentpoint()
+ dc.stroke()
+ dc.selectfont("Nimbus Sans L", 0, 0)
+ dc.setfontsize(12)
+ dc.moveto(50, 10)
+ dc.showtext(u"\u00a1hello, world!")
+ print dc.textextents(u"\u00a1hello, world!")
+
+win = x3.window(0, "foo", my_callback)
+
+m = x3.menu(win, "bar")
+
+x3.menuitem(m, "baz", "bazz", "<ctrl>b")
+x3.menusep(m)
+x3.menuitem(m, "Quit", "quit", "<ctrl>q")
+
+v = x3.vbox(win, 0, 12)
+
+v.setpacking(True, False, 0)
+x3.button(v, "butt", u"\u00a1hello!")
+x3.edittext(v, "quux")
+v.setpacking(True, True, 0)
+x3.view(v, 263, my_viewclient())
+
+x3.main()
diff --git a/third_party/spiro/x3/pyrex/x3.pyx b/third_party/spiro/x3/pyrex/x3.pyx
new file mode 100644
index 0000000..917df94
--- /dev/null
+++ b/third_party/spiro/x3/pyrex/x3.pyx
@@ -0,0 +1,259 @@
+cdef extern from "Python.h":
+ void *PyMem_Malloc(int size) except NULL
+ void Py_INCREF(object)
+ int PyUnicode_Check(object str)
+ object PyUnicode_AsUTF8String(object str)
+
+cdef extern from "x3.h":
+ ctypedef struct x3widget
+ ctypedef struct x3dc:
+ int x
+ int y
+ int width
+ int height
+ cdef enum x3windowflags:
+ x3window_main = 1
+ x3window_dialog = 2
+ ctypedef struct x3viewclient:
+ void (*destroy)(x3viewclient *self)
+ void (*mouse)(x3viewclient *self, int buttons, int mods,
+ double x, double y)
+ int (*key)(x3viewclient *self, char *keyname, int mods, int key)
+ void (*draw)(x3viewclient *self, x3dc *dc)
+
+ # client extension
+ void *py_client
+ ctypedef struct x3extents:
+ double x_bearing
+ double y_bearing
+ double width
+ double height
+ double x_advance
+ double y_advance
+ void x3init(int argc, char **argv)
+ void x3main()
+ x3widget *x3window(x3windowflags flags, char *name,
+ int (callback)(x3widget *w, void *data, char *cmd, char *what, char *arg, void *more),
+ void *data)
+ x3widget *x3menu(x3widget *parent, char *name)
+ x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut)
+ x3widget *x3menusep(x3widget *parent)
+ x3widget *x3align(x3widget *parent, int alignment)
+ x3widget *x3pad(x3widget *parent, int t, int b, int l, int r)
+ x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing)
+ x3widget *x3hpane(x3widget *parent)
+ x3widget *x3vpane(x3widget *parent)
+ x3widget *x3button(x3widget *parent, char *name, char *label)
+ x3widget *x3label(x3widget *parent, char *text)
+ x3widget *x3edittext(x3widget *parent, char *cmd)
+ x3widget *x3view(x3widget *parent, int flags, x3viewclient *vc)
+ void x3viewclient_init(x3viewclient *vc)
+ void x3view_dirty(x3widget *w)
+ void x3view_scrollto(x3widget *w, int x, int y, int width, int height)
+
+ void x3window_setdefaultsize(x3widget *w, int x, int y)
+ void x3setactive(x3widget *w, int active)
+ void x3setpacking(x3widget *w, int fill, int expand, int padding)
+ void x3pane_setsizing(x3widget *w, int c1r, int c1s, int c2r, int c2s)
+ int x3hasfocus(x3widget *w)
+
+ void x3moveto(x3dc *dc, double x, double y)
+ void x3lineto(x3dc *dc, double x, double y)
+ void x3curveto(x3dc *dc,
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3)
+ void x3closepath(x3dc *dc)
+ void x3rectangle(x3dc *dc, double x, double y, double width, double height)
+ void x3getcurrentpoint(x3dc *dc, double *px, double *py)
+ void x3setrgba(x3dc *dc, unsigned int rgba)
+ void x3setlinewidth(x3dc *dc, double w)
+ void x3fill(x3dc *dc)
+ void x3stroke(x3dc *dc)
+ void x3selectfont(x3dc *dc, char *fontname, int slant, int weight)
+ void x3setfontsize(x3dc *dc, double size)
+ void x3showtext(x3dc *dc, char *text)
+ void x3textextents(x3dc *dc, char *text, x3extents *extents)
+ X3_GMW(g, m, w)
+
+cdef object strmaybenull(char *str):
+ if str == NULL:
+ return None
+ else:
+ return str
+
+cdef object utf8(object ustr):
+ if PyUnicode_Check(ustr):
+ return PyUnicode_AsUTF8String(ustr)
+ else:
+ return ustr
+
+ctypedef struct x3viewclient_py:
+ x3viewclient base
+ # client extension
+ void *py_client
+
+cdef class widget:
+ cdef x3widget *w
+ def hasfocus(self):
+ return x3hasfocus(self.w)
+
+cdef int x3py_callback(x3widget *window, void *data, char *cmd, char *what,
+ char *arg, void *more):
+ callback = <object>data
+ callback(cmd, strmaybenull(what), strmaybenull(arg), None)
+
+cdef class window(widget):
+ def __new__(self, int flags, char *name, callback):
+ self.w = x3window(flags, name, x3py_callback, <void *>callback)
+ def setdefaultsize(self, int width, int height):
+ x3window_setdefaultsize(self.w, width, height)
+
+cdef class menu(widget):
+ def __new__(self, widget parent, name):
+ uname = utf8(name)
+ self.w = x3menu(parent.w, uname)
+
+cdef class menuitem(widget):
+ def __new__(self, widget parent, name, char *cmd, char *shortcut = NULL):
+ uname = utf8(name)
+ self.w = x3menuitem(parent.w, uname, cmd, shortcut)
+
+cdef class menusep(widget):
+ def __new__(self, widget parent):
+ self.w = x3menusep(parent.w)
+
+cdef class align(widget):
+ def __new__(self, widget parent, int alignment):
+ self.w = x3align(parent.w, alignment)
+
+cdef class pad(widget):
+ def __new__(self, widget parent, int t, int b, int l, int r):
+ self.w = x3pad(parent.w, t, b, l, r)
+
+cdef class vbox(widget):
+ def __new__(self, widget parent, homogeneous, int spacing):
+ self.w = x3vbox(parent.w, not not homogeneous, spacing)
+ def setpacking(self, fill, expand, int padding):
+ x3setpacking(self.w, not not fill, not not expand, padding)
+
+cdef class hpane(widget):
+ def __new__(self, widget parent):
+ self.w = x3hpane(parent.w)
+ def setsizing(self, c1r, c1s, c2r, c2s):
+ x3pane_setsizing(self.w, not not c1r, not not c1s, not not c2r, not not c2s)
+
+cdef class vpane(widget):
+ def __new__(self, widget parent):
+ self.w = x3vpane(parent.w)
+ def setsizing(self, c1r, c1s, c2r, c2s):
+ x3pane_setsizing(self.w, not not c1r, not not c1s, not not c2r, not not c2s)
+
+cdef class button(widget):
+ def __new__(self, widget parent, char *name, label):
+ ulabel = utf8(label)
+ self.w = x3button(parent.w, name, ulabel)
+
+cdef class label(widget):
+ def __new__(self, widget parent, text):
+ ulabel = utf8(text)
+ self.w = x3label(parent.w, ulabel)
+
+cdef class edittext(widget):
+ def __new__(self, widget parent, char *cmd):
+ self.w = x3edittext(parent.w, cmd)
+
+cdef int x3py_key(x3viewclient *self, char *keyname, int mods, int key):
+ py_client = <object>(<x3viewclient_py *>self).py_client
+ return py_client.key(keyname, mods, key)
+
+cdef void x3py_mouse(x3viewclient *self, int button, int mods, double x, double y):
+ py_client = <object>(<x3viewclient_py *>self).py_client
+ py_client.mouse(button, mods, x, y)
+
+cdef int dbl_to_byte(double x):
+ if x <= 0: return 0
+ if x >= 1: return 255
+ return x * 255 + 0.5
+
+cdef class x3dc_wrap:
+ cdef x3dc *dc
+
+ property rect:
+ def __get__(self):
+ return (self.dc.x, self.dc.y, self.dc.width, self.dc.height)
+
+ def moveto(self, double x, double y):
+ x3moveto(self.dc, x, y)
+ def lineto(self, double x, double y):
+ x3lineto(self.dc, x, y)
+ def curveto(self, double x1, double y1, double x2, double y2, double x3, double y3):
+ x3curveto(self.dc, x1, y1, x2, y2, x3, y3)
+ def closepath(self):
+ x3closepath(self.dc)
+ def rectangle(self, double x, double y, double width, double height):
+ x3rectangle(self.dc, x, y, width, height)
+ def currentpoint(self):
+ cdef double x
+ cdef double y
+ x3getcurrentpoint(self.dc, &x, &y)
+ return (x, y)
+ def setrgba(self, double r, double g, double b, double a):
+ x3setrgba(self.dc, (dbl_to_byte(r) << 24) |
+ (dbl_to_byte(g) << 16) |
+ (dbl_to_byte(b) << 8) |
+ dbl_to_byte(a))
+ def setlinewidth(self, double w):
+ x3setlinewidth(self.dc, w)
+ def fill(self):
+ x3fill(self.dc)
+ def stroke(self):
+ x3stroke(self.dc)
+ def selectfont(self, char *name, int slant, int weight):
+ x3selectfont(self.dc, name, slant, weight)
+ def setfontsize(self, double size):
+ x3setfontsize(self.dc, size)
+ def showtext(self, text):
+ utext = utf8(text)
+ x3showtext(self.dc, utext)
+ def textextents(self, text):
+ cdef x3extents ext
+ utext = utf8(text)
+ x3textextents(self.dc, utext, &ext)
+ return (ext.x_bearing, ext.y_bearing,
+ ext.width, ext.height,
+ ext.x_advance, ext.y_advance)
+
+
+cdef void x3py_draw(x3viewclient *self, x3dc *dc):
+ cdef x3dc_wrap dc_wrap
+ py_client = <object>(<x3viewclient_py *>self).py_client
+ dc_wrap = x3dc_wrap()
+ dc_wrap.dc = dc
+ py_client.draw(dc_wrap)
+
+cdef class view(widget):
+ def __new__(self, widget parent, int flags, viewclient):
+ cdef x3viewclient_py *vc
+ vc = <x3viewclient_py *>PyMem_Malloc(sizeof(x3viewclient_py))
+ x3viewclient_init(&vc.base)
+ vc.py_client = <void *>viewclient
+ Py_INCREF(viewclient)
+ vc.base.key = x3py_key
+ vc.base.mouse = x3py_mouse
+ vc.base.draw = x3py_draw
+ self.w = x3view(parent.w, flags, &vc.base)
+ viewclient.view = self
+ def dirty(self):
+ x3view_dirty(self.w)
+ def scrollto(self, int x, int y, int width, int height):
+ x3view_scrollto(self.w, x, y, width, height)
+
+def main():
+ x3main()
+
+def gmw(g, m, w):
+ return X3_GMW(g, m, w)
+platform = gmw("gtk", "mac", "win32")
+
+x3init(0, NULL)
diff --git a/third_party/spiro/x3/test.c b/third_party/spiro/x3/test.c
new file mode 100644
index 0000000..53fae33
--- /dev/null
+++ b/third_party/spiro/x3/test.c
@@ -0,0 +1,158 @@
+/* A test app for X3. */
+
+#include "x3.h"
+#include <stdlib.h>
+
+int
+mycallback(x3widget *w, void *data,
+ char *cmd, char *what, char *arg, void *more)
+{
+ printf("my callback: cmd=\"%s\", what=\"%s\", arg=\"%s\"\n",
+ cmd, what ? what : "(null)", arg ? arg : "(null)");
+ return 1;
+}
+
+#if defined(X3_GTK) || defined(X3_CARBON)
+static void test_viewclient_draw(x3viewclient *self, x3dc *dc)
+{
+ if (dc->buf) {
+ int i, j;
+
+ for (i = 0; i < dc->height; i++) {
+ for (j = 0; j < dc->width; j++) {
+ dc->buf[i * dc->rowstride + j * 3] = i;
+ dc->buf[i * dc->rowstride + j * 3 + 1] = 0x80;
+ dc->buf[i * dc->rowstride + j * 3 + 2] = j;
+ }
+ }
+ } else {
+ x3extents ext;
+
+ x3moveto(dc, 10, 10);
+ x3curveto(dc, 200, 10, 100, 100, 490, 100);
+ x3stroke(dc);
+ x3selectfont(dc, "Nimbus Roman No9 L", 0, 0);
+ x3moveto(dc, 10, 50);
+ x3setfontsize(dc, 16);
+ x3showtext(dc, "hello, world!");
+ x3textextents(dc, "hello, world!", &ext);
+ printf("text extents: %g %g %g %g %g %g\n",
+ ext.x_bearing, ext.y_bearing,
+ ext.width, ext.height,
+ ext.x_advance, ext.y_advance);
+ }
+}
+
+static int test_viewclient_key(x3viewclient *self,
+ char *keyname, int mods, int key)
+{
+ printf("view key: %s %d %d\n", keyname, mods, key);
+ return 1;
+}
+
+static void test_viewclient_mouse(x3viewclient *self,
+ int button, int mods,
+ double x, double y)
+{
+ printf("view button: %d %d %g %g\n", button, mods, x, y);
+}
+
+x3viewclient *test_viewclient(void)
+{
+ x3viewclient *result = (x3viewclient *)malloc(sizeof(x3viewclient));
+ x3viewclient_init(result);
+ result->draw = test_viewclient_draw;
+ result->key = test_viewclient_key;
+ result->mouse = test_viewclient_mouse;
+
+ return result;
+}
+
+#ifdef X3_USEMAIN
+int
+main(int argc, char **argv)
+{
+ x3widget *mainwin;
+ x3widget *pane;
+ x3widget *vbox;
+ x3widget *m;
+ x3viewflags viewflags = x3view_click | x3view_hover | x3view_key;
+
+ x3init(&argc, &argv);
+ mainwin = x3window(x3window_main, "untitled", mycallback, NULL);
+#if 1
+ m = x3menu(mainwin, "F\xc2\xa1le");
+ x3menuitem(m, "Save", "save", "<cmd>s");
+ x3setactive(x3menuitem(m, "Save As...", "sava", "<cmd>S"), 0);
+ x3menusep(m);
+ x3menuitem(m, "Preferences...", "pref", "<cmd>,");
+
+ m = x3menu(mainwin, "Edit");
+
+ x3menuitem(m, "ctrl-delete", "cdel", "<ctl>Delete");
+ x3menuitem(m, "ctrl-f1", "cf1", "<ctl>F1");
+ if (0) {
+ int i, j;
+
+ for (j = 0; j < 3; j++) {
+ char mname[16];
+ mname[0] = '0' + j;
+ mname[1] = 0;
+ m = x3menu(mainwin, mname);
+ for (i = 0x20 + 0x20 * j; i < 0x40 + 0x20 * j; i++) {
+ char name[16];
+
+ name[0] = i;
+ name[1] = 0;
+ x3menuitem(m, name, name, name);
+ }
+ }
+ }
+#endif
+
+#if 1
+ pane = x3vpane(mainwin);
+ vbox = x3vbox(x3pad(pane, 10, 10, 10, 10), 0, 5);
+
+ x3setpacking(vbox, FALSE, FALSE, 0);
+ x3button(x3align(vbox, x3center), "foo", "Click me!");
+ x3button(vbox, "bar", "Click me too!");
+ x3setactive(x3button(vbox, "bar", "But not me"), 0);
+ x3label(vbox, "I am a label.");
+ x3edittext(vbox, "etxt");
+#endif
+
+ x3setpacking(vbox, TRUE, TRUE, 0);
+ viewflags |= x3view_2d;
+ viewflags |= x3view_scroll;
+ x3view(pane, viewflags, test_viewclient());
+
+#if 0
+ x3_window_show(mainwin);
+#endif
+
+ x3main();
+ return 0;
+}
+#endif
+#endif
+
+#ifdef X3_USEWINMAIN
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,
+ int iCmdShow)
+{
+ //printf("winmain\n");
+ x3widget *mainwin;
+ x3widget *vbox;
+
+ x3init_win32(hInstance);
+
+ mainwin = x3window(x3window_main, "untitled", mycallback, NULL);
+ x3_window_show(mainwin);
+ vbox = x3vbox(mainwin, 0, 5);
+ x3button(vbox, "foo", "Click me!");
+ x3button(vbox, "bar", "And me too!");
+ x3main();
+ return 0;
+}
+#endif
diff --git a/third_party/spiro/x3/x3.h b/third_party/spiro/x3/x3.h
new file mode 100644
index 0000000..a056583
--- /dev/null
+++ b/third_party/spiro/x3/x3.h
@@ -0,0 +1,293 @@
+/* X3 is a lightweight toolset for building cross-platform applications. */
+
+typedef struct _x3widget x3widget;
+
+typedef struct _x3viewclient x3viewclient;
+
+typedef enum {
+ x3window_main = 1,
+ x3window_dialog = 2,
+} x3windowflags;
+
+typedef enum {
+ x3center = 0,
+ x3left = 1,
+ x3right = 2,
+ x3hfill = 3,
+ x3top = 4,
+ x3bottom = 8,
+ x3vfill = 12,
+
+ x3topleft = 5,
+ x3topright = 6,
+ x3bottomleft = 9,
+ x3bottomright = 10,
+} x3alignment;
+
+typedef enum {
+ x3view_click = 1,
+ x3view_hover = 2,
+ x3view_key = 4,
+ x3view_2d = 0x100,
+ x3view_rgb = 0x200,
+ x3view_scroll = 0x10000
+} x3viewflags;
+
+typedef struct _x3dc x3dc;
+
+struct _x3viewclient {
+ void (*destroy)(x3viewclient *self);
+ void (*mouse)(x3viewclient *self, int buttons, int mods,
+ double x, double y);
+ int (*key)(x3viewclient *self, char *keyname, int mods, int key);
+ void (*draw)(x3viewclient *self, x3dc *dc);
+};
+
+/* Windows and Carbon both use left/top/right/bottom nomenclature,
+ at least for int rects, while gtk+ uses x/y/width/height. */
+typedef struct {
+ double x0;
+ double y0;
+ double x1;
+ double y1;
+} x3rect;
+
+#ifdef X3_GTK
+
+#include <gtk/gtk.h>
+
+#define X3_GMW(g, m, w) g
+
+struct _x3dc {
+ /* move to x3rect structure? */
+ int x;
+ int y;
+ int width;
+ int height;
+
+ cairo_t *cr;
+
+ /* for rgb drawing */
+ unsigned char *buf;
+ int rowstride;
+};
+
+struct _x3widget {
+ char *name;
+ x3widget *parent;
+ GtkWidget *widget;
+};
+
+typedef cairo_text_extents_t x3extents;
+
+#define X3_SHIFT_MASK GDK_SHIFT_MASK
+#define X3_CONTROL_MASK GDK_CONTROL_MASK
+#define X3_ALT_MASK GDK_MOD1_MASK
+/* there is no X3_CMD_MASK in gtk */
+
+#define X3_USEMAIN
+
+#endif
+
+#ifdef X3_CARBON
+
+#define X3_GMW(g, m, w) m
+
+#include <Carbon/Carbon.h>
+
+struct _x3dc {
+ /* move to x3rect structure? */
+ int x;
+ int y;
+ int width;
+ int height;
+
+ CGContextRef ctx;
+ CGMutablePathRef path;
+
+ /* for rgb drawing */
+ char *buf;
+ int rowstride;
+};
+
+typedef struct _x3type x3type;
+
+struct _x3type {
+ void (*sizereq)(x3widget *w);
+ void (*sizealloc)(x3widget *w, x3rect *r);
+ void (*add)(x3widget *w, x3widget *child);
+};
+
+typedef enum {
+ x3carbonnone,
+ x3carbonhiview,
+ x3carbonwindow,
+ x3carbonmenu,
+ x3carbonmenuitem,
+} x3carbonvar;
+
+typedef enum {
+ x3flag_needsizereq = 1,
+ x3flag_needsizealloc = 2
+} x3widgetflags;
+
+struct _x3widget {
+ const x3type *type;
+ x3widgetflags flags;
+ x3rect sizerequest;
+ char *name;
+ x3widget *parent;
+ x3carbonvar var;
+ union {
+ HIViewRef hiview;
+ WindowRef window;
+ MenuRef menu;
+ int menuitem;
+ } u;
+ int n_children;
+ x3widget **children;
+};
+
+typedef struct {
+ double x_bearing;
+ double y_bearing;
+ double width;
+ double height;
+ double x_advance;
+ double y_advance;
+} x3extents;
+
+#define X3_SHIFT_MASK shiftKey
+#define X3_CONTROL_MASK controlKey
+#define X3_CMD_MASK cmdKey
+#define X3_ALT_MASK optionKey
+
+/* todo: figure out how to plumb mouse event */
+#define X3_BUTTON1_MASK 0
+#define X3_2BUTTON_PRESS 0
+#define X3_3BUTTON_PRESS 0
+
+#define X3_USEMAIN
+
+#endif
+
+#ifdef X3_WIN32
+
+#include <windows.h>
+
+#define X3_GMW(g, m, w) w
+
+typedef struct _x3type x3type;
+
+struct _x3type {
+ void (*sizereq)(x3widget *w);
+ void (*sizealloc)(x3widget *w, x3rect *r);
+ void (*add)(x3widget *w, x3widget *child);
+};
+
+typedef enum {
+ x3winnone,
+ x3winhwnd,
+} x3winvar;
+
+typedef enum {
+ x3flag_needsizereq = 1,
+ x3flag_needsizealloc = 2
+} x3widgetflags;
+
+typedef struct {
+ double x_bearing;
+ double y_bearing;
+ double width;
+ double height;
+ double x_advance;
+ double y_advance;
+} x3extents;
+
+struct _x3widget {
+ const x3type *type;
+ char *name;
+ x3widget *parent;
+ x3widgetflags flags;
+ x3rect sizerequest;
+ x3winvar var; // should this be in the type?
+ union {
+ HWND hwnd;
+ } u;
+ int n_children;
+ x3widget **children;
+};
+
+#define X3_USEWINMAIN
+
+void x3init_win32(HINSTANCE hInstance);
+
+#endif
+
+typedef int (*x3window_callback)(x3widget *window, void *data,
+ char *cmd, char *what, char *arg,
+ void *more);
+
+/* Main loop */
+void x3init(int *pargc, char ***pargv);
+void x3main(void);
+
+x3widget *x3window(x3windowflags flags, char *label,
+ x3window_callback callback, void *callback_data);
+x3widget *x3menu(x3widget *parent, char *name);
+x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut);
+x3widget *x3menusep(x3widget *parent);
+x3widget *x3align(x3widget *parent, x3alignment alignment);
+x3widget *x3pad(x3widget *parent, int t, int b, int l, int r);
+x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing);
+x3widget *x3hpane(x3widget *parent);
+x3widget *x3vpane(x3widget *parent);
+x3widget *x3button(x3widget *parent, char *name, char *label);
+x3widget *x3label(x3widget *parent, char *label);
+x3widget *x3edittext(x3widget *parent, char *cmd);
+x3widget *x3view(x3widget *parent, x3viewflags flags, x3viewclient *vc);
+void x3view_dirty(x3widget *w);
+void x3view_scrollto(x3widget *w, int x, int y, int width, int height);
+
+void x3viewclient_init(x3viewclient *vc);
+
+void x3window_setdefaultsize(x3widget *w, int width, int height);
+
+void x3pane_setsizing(x3widget *w, int child1_resize, int child1_shrink,
+ int child2_resize, int child2_shrink);
+
+void x3setactive(x3widget *w, int active);
+int x3hasfocus(x3widget *w);
+void x3setpacking(x3widget *w, int fill, int expand, int padding);
+
+extern int x3n_winopen;
+
+/* 2d drawing functions */
+
+void x3moveto(x3dc *dc, double x, double y);
+void x3lineto(x3dc *dc, double x, double y);
+void x3curveto(x3dc *dc,
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3);
+void x3closepath(x3dc *dc);
+void x3rectangle(x3dc *dc, double x, double y, double width, double height);
+void x3getcurrentpoint(x3dc *dc, double *px, double *py);
+void x3setrgba(x3dc *dc, unsigned int rgba);
+void x3setlinewidth(x3dc *dc, double w);
+void x3fill(x3dc *dc);
+void x3stroke(x3dc *dc);
+
+/* This is an overly simple text api, based on cairo's. It may be phased
+ out in favor of a more capable one. */
+void x3selectfont(x3dc *dc, char *fontname, int slant, int weight);
+void x3setfontsize(x3dc *dc, double size);
+void x3showtext(x3dc *dc, char *text);
+void x3textextents(x3dc *dc, char *text, x3extents *extents);
+
+#if defined(X3_CARBON) || defined(X3_WIN32)
+/* Internals for carbon/win32 */
+void x3widget_init(x3widget *w, const x3type *type);
+void x3add_default(x3widget *parent, x3widget *child);
+void x3add(x3widget *parent, x3widget *child);
+#endif
diff --git a/third_party/spiro/x3/x3carbon.c b/third_party/spiro/x3/x3carbon.c
new file mode 100644
index 0000000..7929e47
--- /dev/null
+++ b/third_party/spiro/x3/x3carbon.c
@@ -0,0 +1,1002 @@
+#include "x3.h"
+#include "x3common.h"
+
+/* Globals for managing sync */
+
+#define VERBOSE
+
+#define kX3ViewClassID CFSTR("com.levien.x3.X3View")
+#define kX3ViewPrivate 'X3_v'
+
+/* Some utility-type functions. */
+
+UInt32 x3mkmultichar(const char *s)
+{
+ int len = strlen(s);
+ int i;
+ UInt32 result = 0;
+
+ for (i = 0; i < (len > 4 ? 4 : len); i++)
+ result = (result << 8) + (unsigned char)s[i];
+ for (; i < 4; i++)
+ result = (result << 8) + ' ';
+ return result;
+}
+
+char *x3multicharstr(UInt32 mc, char buf[5])
+{
+ int i, len;
+
+ for (i = 0; i < 4; i++)
+ if (((mc >> (8 * i)) & 0xff) != ' ') break;
+ len = 4 - i;
+ for (i = 0; i < len; i++)
+ buf[i] = (mc >> (24 - 8 * i)) & 0xff;
+ buf[len] = 0;
+ return buf;
+}
+
+void x3widget_init(x3widget *w, const x3type *type)
+{
+ w->type = type;
+ w->name = NULL;
+ w->parent = NULL;
+ w->var = x3carbonnone;
+ w->u.window = NULL;
+ w->n_children = 0;
+ w->children = NULL;
+}
+
+static x3widget *x3widget_new_container(x3widget *parent, char *name,
+ const x3type *type)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+ x3widget_init(result, type);
+ result->name = name ? strdup(name) : NULL;
+ x3add(parent, result);
+ x3qsizereq(result);
+ return result;
+}
+
+static x3widget *x3widget_new_hiview(x3widget *parent, char *name,
+ const x3type *type,
+ HIViewRef hiview)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+ x3widget_init(result, type);
+ result->name = name ? strdup(name) : NULL;
+ result->var = x3carbonhiview;
+ result->u.hiview = hiview;
+ x3add(parent, result);
+ x3qsizereq(result);
+ return result;
+}
+
+static x3widget *x3widget_new_menu(x3widget *parent,
+ const x3type *type,
+ MenuRef menu)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+ x3widget_init(result, type);
+ result->var = x3carbonmenu;
+ result->u.menu = menu;
+ x3add(parent, result);
+ return result;
+}
+
+static x3widget *x3widget_new_menuitem(x3widget *parent,
+ const x3type *type, int index)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+ x3widget_init(result, type);
+ x3add(parent, result);
+ result->var = x3carbonmenuitem;
+ result->u.menuitem = index;
+ return result;
+}
+
+void x3init(int *pargc, char ***pargv)
+{
+ ProcessSerialNumber psn;
+
+ /* Most apps get this done from loading the menu bar from the NIB.
+ Prior to 10.3, there was an undocumented call. This one is official
+ for 10.3 and later. */
+ GetCurrentProcess(&psn);
+ TransformProcessType(&psn, kProcessTransformToForegroundApplication);
+ SetFrontProcess(&psn);
+
+ x3initqs();
+}
+
+void x3main(void)
+{
+ x3sync();
+ RunApplicationEventLoop();
+}
+
+void x3_window_show(x3widget *mainwin)
+{
+ ControlRef root;
+#if 0
+ TransitionWindow(mainwin->u.window, kWindowZoomTransitionEffect,
+ kWindowShowTransitionAction, NULL);
+#endif
+
+#if 0
+ CreateRootControl(mainwin->u.window, &root);
+#endif
+ //RepositionWindow(mainwin->u.window, NULL, kWindowCascadeOnMainScreen);
+ ShowWindow(mainwin->u.window);
+ SelectWindow(mainwin->u.window);
+}
+
+static WindowRef x3window_of(x3widget *w)
+{
+ while (w->parent) w = w->parent;
+ return w->var == x3carbonwindow ? w->u.window : NULL;
+}
+
+typedef struct {
+ x3widget base;
+ x3window_callback callback;
+ void *callback_data;
+} x3widget_window;
+
+pascal OSStatus x3carbonWindowEventHandler(EventHandlerCallRef cr,
+ EventRef inEvent, void *data)
+{
+ x3widget_window *z = (x3widget_window *)data;
+ OSStatus result = noErr;
+ UInt32 eclass = GetEventClass(inEvent);
+ UInt32 ekind = GetEventKind(inEvent);
+ char multicharbuf[5];
+
+ if (eclass == kEventClassCommand && ekind == kEventCommandProcess) {
+ HICommand command;
+ int status;
+
+ GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand,
+ NULL, sizeof(HICommand), NULL, &command);
+ status = z->callback(&z->base, z->callback_data,
+ x3multicharstr(command.commandID, multicharbuf),
+ "command", NULL, NULL);
+ if (status == 1)
+ result = eventNotHandledErr;
+ } else if (eclass = kEventClassWindow && ekind == kEventWindowBoundsChanged) {
+ /* todo: only queue size request when size changes, not just pos */
+ x3qsizereq(&z->base);
+ x3sync();
+ } else {
+ printf("My handler is getting called %s %d!\n",
+ x3multicharstr(eclass, multicharbuf),
+ GetEventKind(inEvent));
+ }
+ result = eventNotHandledErr;
+ return result;
+}
+
+void x3window_sizereq(x3widget *w)
+{
+}
+
+void x3window_sizealloc(x3widget *w, x3rect *r)
+{
+ int i;
+ Rect bounds;
+ x3rect child_r;
+
+ GetWindowBounds(w->u.window, kWindowContentRgn, &bounds);
+ child_r.x0 = 0;
+ child_r.x1 = bounds.right - bounds.left;
+ child_r.y0 = 0;
+ child_r.y1 = bounds.bottom - bounds.top;
+ printf("x3window_sizealloc (%d, %d) - (%d, %d)\n",
+ bounds.left, bounds.top, bounds.right, bounds.bottom);
+ for (i = 0; i < w->n_children; i++) {
+ x3widget *child = w->children[i];
+ if (child->type->sizealloc)
+ child->type->sizealloc(child, &child_r);
+ child->flags &= ~x3flag_needsizealloc;
+ }
+}
+
+x3type x3windowtype = { x3window_sizereq,
+ x3window_sizealloc,
+ x3add_default };
+
+x3widget *x3window(x3windowflags flags, char *label,
+ x3window_callback callback, void *data)
+{
+ WindowRef window;
+ Rect bounds = { 100, 100, 400, 600 };
+ EventHandlerRef handlerRef;
+ WindowAttributes attrs =
+ kWindowCompositingAttribute |
+ kWindowLiveResizeAttribute |
+ kWindowInWindowMenuAttribute |
+ kWindowFrameworkScaledAttribute |
+ kWindowStandardHandlerAttribute;
+ EventTypeSpec windowEvents[] = {
+ { kEventClassCommand, kEventCommandProcess },
+ //{ kEventClassCommand, kEventCommandUpdateStatus },
+
+ //{ kEventClassMouse, kEventMouseDown },
+
+ //{ kEventClassWindow, kEventWindowClose },
+ //{ kEventClassWindow, kEventWindowGetIdealSize },
+ { kEventClassWindow, kEventWindowBoundsChanged },
+ //{ kEventClassWindow, kEventWindowGetClickActivation },
+ //{ kEventClassWindow, kEventWindowContextualMenuSelect }
+ };
+ CFStringRef cflabel = CFStringCreateWithCString(NULL, label,
+ kCFStringEncodingUTF8);
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget_window));
+
+ if (flags & x3window_main)
+ attrs |= kWindowStandardDocumentAttributes;
+
+ OSStatus err = CreateNewWindow(kDocumentWindowClass, attrs,
+ &bounds, &window);
+
+ SetWindowTitleWithCFString(window, cflabel);
+ CFRelease(cflabel);
+ x3widget_init(result, &x3windowtype);
+ result->var = x3carbonwindow;
+ result->u.window = window;
+ ((x3widget_window *)result)->callback = callback;
+ ((x3widget_window *)result)->callback_data = data;
+
+ InstallWindowEventHandler(window, NewEventHandlerUPP(x3carbonWindowEventHandler),
+ sizeof(windowEvents)/sizeof(EventTypeSpec),
+ windowEvents, result, &handlerRef);
+
+ x3qshow(result);
+ return result;
+}
+
+x3type x3menutype = { NULL, NULL, x3add_default };
+
+x3widget *x3menu(x3widget *parent, char *name)
+{
+ static int id = 1; /* Note: menu id should probably be kept per-window */
+ MenuRef menu;
+ CFStringRef cflabel = CFStringCreateWithCString(NULL, name,
+ kCFStringEncodingUTF8);
+
+ CreateNewMenu(id++, 0, &menu);
+ SetMenuTitleWithCFString(menu, cflabel);
+ CFRelease(cflabel);
+ InsertMenu(menu, 0);
+ return x3widget_new_menu(parent, &x3menutype, menu);
+}
+
+int x3parseshortcut(const char *shortcut, UInt16 *pkey, UInt8 *pmods)
+{
+ UInt16 key;
+ UInt8 mods = kMenuNoCommandModifier;
+ int i = 0;
+
+ while (shortcut[i] == '<') {
+ if (!strncmp(shortcut + i, "<cmd>", 5)) {
+ mods &= ~kMenuNoCommandModifier;
+ i += 5;
+ } else if (!strncmp(shortcut + i, "<shift>", 7)) {
+ mods |= kMenuShiftModifier;
+ i += 7;
+ } else if (!strncmp(shortcut + i, "<option>", 8)) {
+ mods |= kMenuOptionModifier;
+ i += 8;
+ } else if (!strncmp(shortcut + i, "<ctrl>", 6)) {
+ mods |= kMenuControlModifier;
+ i += 6;
+ } else
+ return false;
+ }
+ if (shortcut[i] && shortcut[i + 1] == 0) {
+ key = shortcut[i];
+ if (key >= 'a' && key <= 'z') key -= 'a' - 'A';
+ else if (key >= 'A' && key <= 'Z') mods |= kMenuShiftModifier;
+ } else
+ return false;
+
+ *pkey = key;
+ *pmods = mods;
+ return true;
+}
+
+x3type x3menuitemtype = { NULL, NULL, x3add_default };
+
+x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut)
+{
+ CFStringRef cflabel = CFStringCreateWithCString(NULL, name,
+ kCFStringEncodingUTF8);
+ MenuItemIndex index;
+
+ AppendMenuItemTextWithCFString(parent->u.menu, cflabel,
+ 0,
+ x3mkmultichar(cmd), &index);
+ if (shortcut) {
+ UInt16 key;
+ UInt8 mods;
+
+ if (x3parseshortcut(shortcut, &key, &mods)) {
+ SetMenuItemCommandKey(parent->u.menu, index, false, key);
+ SetMenuItemModifiers(parent->u.menu, index, mods);
+ }
+ }
+ CFRelease(cflabel);
+ return x3widget_new_menuitem(parent, &x3menuitemtype, index);
+}
+
+x3widget *x3menusep(x3widget *parent)
+{
+ Str255 str = {1, '-'};
+
+ AppendMenu(parent->u.menu, str);
+ return x3widget_new_menuitem(parent, &x3menuitemtype, -1);
+}
+
+void x3button_sizereq(x3widget *w)
+{
+ SInt16 offset;
+ Rect r = { 0, 0, 0, 0 };
+
+ GetBestControlRect(w->u.hiview, &r, &offset);
+ w->sizerequest.x0 = r.left;
+ w->sizerequest.y0 = r.top;
+ w->sizerequest.x1 = r.right;
+ w->sizerequest.y1 = r.bottom;
+#ifdef VERBOSE
+ printf("button sizereq = (%g, %g) - (%g, %g)\n",
+ w->sizerequest.x0, w->sizerequest.y0,
+ w->sizerequest.x1, w->sizerequest.y1);
+#endif
+}
+
+void x3button_sizealloc(x3widget *w, x3rect *r)
+{
+ Rect bounds;
+
+ bounds.left = r->x0;
+ bounds.top = r->y0;
+ bounds.right = r->x1;
+ bounds.bottom = r->y1;
+ /* TODO probably want to use HIViewSetFrame instead */
+ printf("button sizealloc = (%g, %g) - (%g, %g)\n",
+ r->x0, r->y0, r->x1, r->y1);
+ SetControlBounds(w->u.hiview, &bounds);
+}
+
+x3type x3buttontype = { x3button_sizereq,
+ x3button_sizealloc,
+ x3add_default };
+
+x3widget *x3button(x3widget *parent, char *cmd, char *label)
+{
+ WindowRef window = x3window_of(parent);
+ Rect r = {10, 10, 30, 100};
+ ControlRef control;
+ OSStatus err;
+ CFStringRef cflabel = CFStringCreateWithCString(NULL, label,
+ kCFStringEncodingUTF8);
+
+ err = CreatePushButtonControl(window, &r, cflabel, &control);
+ CFRelease(cflabel);
+ SetControlCommandID(control, x3mkmultichar(cmd));
+ //SetWindowDefaultButton(window, control);
+ return x3widget_new_hiview(parent, cmd, &x3buttontype, control);
+}
+
+x3widget *x3label(x3widget *parent, char *text)
+{
+ WindowRef window = x3window_of(parent);
+ Rect r = {10, 10, 30, 100};
+ ControlRef control;
+ OSStatus err;
+ Boolean singleline = true;
+ CFStringRef cftext = CFStringCreateWithCString(NULL, text,
+ kCFStringEncodingUTF8);
+
+ err = CreateStaticTextControl(window, &r, cftext,
+ NULL, &control);
+ CFRelease(cftext);
+#if 0
+ SetControlData(control, kControlEntireControl,
+ kControlEditTextSingleLineTag, sizeof(Boolean),
+ &singleline);
+#endif
+ return x3widget_new_hiview(parent, NULL, &x3buttontype, control);
+}
+
+x3widget *x3edittext(x3widget *parent, char *cmd)
+{
+ WindowRef window = x3window_of(parent);
+ Rect r = {10, 10, 30, 100};
+ ControlRef control;
+ OSStatus err;
+ Boolean singleline = true;
+
+ err = CreateEditUnicodeTextControl(window, &r, CFSTR(""),
+ false, NULL, &control);
+ SetControlCommandID(control, x3mkmultichar(cmd));
+#if 0
+ SetControlData(control, kControlEntireControl,
+ kControlEditTextSingleLineTag, sizeof(Boolean),
+ &singleline);
+#endif
+ return x3widget_new_hiview(parent, cmd, &x3buttontype, control);
+}
+
+x3widget *x3hpane(x3widget *parent)
+{
+ return NULL;
+}
+
+x3widget *x3vpane(x3widget *parent)
+{
+ return NULL;
+}
+
+typedef struct
+{
+ HIViewRef view;
+ x3viewflags flags;
+ x3viewclient *vc;
+} x3view_data;
+
+static OSStatus
+x3view_construct(EventRef inEvent)
+{
+ OSStatus err;
+ x3view_data *data;
+
+ data = (x3view_data *)malloc(sizeof(x3view_data));
+ require_action(data != NULL, CantMalloc, err = memFullErr);
+ err = GetEventParameter(inEvent, kEventParamHIObjectInstance,
+ typeHIObjectRef, NULL, sizeof(HIObjectRef), NULL,
+ (HIObjectRef *)&data->view);
+ require_noerr(err, ParameterMissing);
+ err = SetEventParameter(inEvent, kEventParamHIObjectInstance,
+ typeVoidPtr, sizeof(x3view_data *), &data);
+
+ data->vc = NULL;
+
+ ParameterMissing:
+ if (err != noErr)
+ free(data);
+
+ CantMalloc:
+ return err;
+}
+
+static OSStatus
+x3view_destruct(EventRef inEvent, x3view_data *inData)
+{
+ free(inData);
+ return noErr;
+}
+
+static OSStatus
+x3view_initialize(EventHandlerCallRef inCallRef, EventRef inEvent,
+ x3view_data *inData)
+{
+ OSStatus err;
+ HIRect bounds;
+
+ err = CallNextEventHandler(inCallRef, inEvent);
+ require_noerr(err, TroubleInSuperClass);
+
+ err = GetEventParameter(inEvent, 'Boun', typeHIRect,
+ NULL, sizeof(HIRect), NULL, &bounds);
+ require_noerr(err, ParameterMissing);
+
+ HIViewSetFrame(inData->view, &bounds);
+
+ ParameterMissing:
+ TroubleInSuperClass:
+ return err;
+}
+
+static OSStatus
+x3view_draw(EventRef inEvent, x3view_data *inData)
+{
+ OSStatus err;
+ CGContextRef ctx;
+
+ err = GetEventParameter(inEvent, kEventParamCGContextRef, typeCGContextRef,
+ NULL, sizeof(CGContextRef), NULL, &ctx);
+ require_noerr(err, ParameterMissing);
+
+#ifdef VERBOSE
+ printf("x3view_draw!\n");
+#endif
+
+ if (inData->vc && inData->vc->draw) {
+ x3dc dc;
+
+ /* set up bounds */
+ if (inData->flags & x3view_2d) {
+ dc.ctx = ctx;
+ dc.path = NULL;
+ dc.buf = NULL;
+
+ inData->vc->draw(inData->vc, &dc);
+ if (dc.path) {
+ CGPathRelease(dc.path);
+ }
+ } else if (inData->flags & x3view_rgb) {
+ /* todo */
+ }
+ }
+
+ ParameterMissing:
+ return err;
+}
+
+static OSStatus
+x3view_get_data(EventRef inEvent, x3view_data *inData)
+{
+ OSStatus err;
+ OSType tag;
+ Ptr ptr;
+ Size outSize;
+
+ /* Probably could use a bit more error checking here, for type
+ and size match. Also, just returning an x3view_data seems a
+ little hacky. */
+ err = GetEventParameter(inEvent, kEventParamControlDataTag, typeEnumeration,
+ NULL, sizeof(OSType), NULL, &tag);
+ require_noerr(err, ParameterMissing);
+
+ err = GetEventParameter(inEvent, kEventParamControlDataBuffer, typePtr,
+ NULL, sizeof(Ptr), NULL, &ptr);
+
+ if (tag == kX3ViewPrivate) {
+ *((x3view_data **)ptr) = inData;
+ outSize = sizeof(x3view_data *);
+ } else
+ err = errDataNotSupported;
+
+ if (err == noErr)
+ err = SetEventParameter(inEvent, kEventParamControlDataBufferSize, typeLongInteger,
+ sizeof(Size), &outSize);
+
+ ParameterMissing:
+ return err;
+}
+
+static OSStatus
+x3view_set_data(EventRef inEvent, x3view_data *inData)
+{
+ OSStatus err;
+ Ptr ptr;
+ OSType tag;
+
+ err = GetEventParameter(inEvent, kEventParamControlDataTag, typeEnumeration,
+ NULL, sizeof(OSType), NULL, &tag);
+ require_noerr(err, ParameterMissing);
+
+ err = GetEventParameter(inEvent, kEventParamControlDataBuffer, typePtr,
+ NULL, sizeof(Ptr), NULL, &ptr);
+ require_noerr(err, ParameterMissing);
+
+ if (tag == 'X3vc') {
+ inData->vc = *(x3viewclient **)ptr;
+ } else if (tag == 'X3vf') {
+ inData->flags = *(x3viewflags *)ptr;
+ } else
+ err = errDataNotSupported;
+
+ ParameterMissing:
+ return err;
+}
+
+static OSStatus
+x3view_hittest(EventRef inEvent, x3view_data *inData)
+{
+ OSStatus err;
+ HIPoint where;
+ HIRect bounds;
+ ControlPartCode part;
+
+ err = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint,
+ NULL, sizeof(HIPoint), NULL, &where);
+ require_noerr(err, ParameterMissing);
+
+ err = HIViewGetBounds(inData->view, &bounds);
+ require_noerr(err, ParameterMissing);
+
+ if (CGRectContainsPoint(bounds, where))
+ part = 1;
+ else
+ part = kControlNoPart;
+ err = SetEventParameter(inEvent, kEventParamControlPart,
+ typeControlPartCode, sizeof(ControlPartCode),
+ &part);
+ printf("hittest %g, %g!\n", where.x, where.y);
+
+ ParameterMissing:
+ return err;
+}
+
+/*
+ If we need more sophisticated tracking (like mixing key events), there's
+ a good discussion here:
+ http://lists.apple.com/archives/Carbon-development/2001/Apr/msg01688.html
+*/
+static OSStatus
+x3view_track(EventRef inEvent, x3view_data *inData)
+{
+ OSStatus err;
+ HIPoint where;
+ MouseTrackingResult mouseStatus;
+ HIRect bounds;
+ Rect windBounds;
+ Point theQDPoint;
+ UInt32 mods;
+
+ err = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint,
+ NULL, sizeof(HIPoint), NULL, &where);
+ require_noerr(err, ParameterMissing);
+
+ err = GetEventParameter(inEvent, kEventParamKeyModifiers, typeUInt32,
+ NULL, sizeof(UInt32), NULL, &mods);
+ require_noerr(err, ParameterMissing);
+
+ err = HIViewGetBounds(inData->view, &bounds);
+ require_noerr(err, ParameterMissing);
+
+ GetWindowBounds(GetControlOwner(inData->view), kWindowStructureRgn, &windBounds);
+
+ if (inData->vc && inData->vc->mouse)
+ inData->vc->mouse(inData->vc, 1, mods, where.x, where.y);
+
+#ifdef VERBOSE
+ printf("press: %g, %g!\n", where.x, where.y);
+#endif
+ //pe_view_button_press(inData, where.x, where.y, 0);
+
+ mouseStatus = kMouseTrackingMouseDown;
+ while (mouseStatus != kMouseTrackingMouseUp) {
+ TrackMouseLocation(NULL, &theQDPoint, &mouseStatus);
+ where.x = theQDPoint.h - windBounds.left;
+ where.y = theQDPoint.v - windBounds.top;
+ HIViewConvertPoint(&where, NULL, inData->view);
+#ifdef VERBOSE
+ printf("track %d: %g, %g!\n", mouseStatus, where.x, where.y);
+#endif
+ if (mouseStatus == kMouseTrackingMouseUp) {
+ if (inData->vc && inData->vc->mouse)
+ inData->vc->mouse(inData->vc, -1, mods, where.x, where.y);
+ } else if (mouseStatus == kMouseTrackingKeyModifiersChanged) {
+ mods = GetCurrentEventKeyModifiers();
+ if (inData->vc && inData->vc->mouse)
+ inData->vc->mouse(inData->vc, 0, mods, where.x, where.y);
+ } else {
+ if (inData->vc && inData->vc->mouse)
+ inData->vc->mouse(inData->vc, 0, mods, where.x, where.y);
+ }
+ }
+
+ ParameterMissing:
+ return err;
+}
+
+pascal OSStatus
+x3view_handler(EventHandlerCallRef inCallRef,
+ EventRef inEvent,
+ void* inUserData )
+{
+ OSStatus err = eventNotHandledErr;
+ UInt32 eventClass = GetEventClass(inEvent);
+ UInt32 eventKind = GetEventKind(inEvent);
+ x3view_data *data = (x3view_data *)inUserData;
+
+ printf("view handler %c%c%c%c %d\n",
+ (eventClass >> 24) & 0xff,
+ (eventClass >> 16) & 0xff,
+ (eventClass >> 8) & 0xff,
+ (eventClass >> 0) & 0xff,
+ eventKind);
+ switch (eventClass) {
+ case kEventClassHIObject:
+ switch (eventKind) {
+ case kEventHIObjectConstruct:
+ err = x3view_construct(inEvent);
+ break;
+ case kEventHIObjectInitialize:
+ err = x3view_initialize(inCallRef, inEvent, data);
+ break;
+ case kEventHIObjectDestruct:
+ err = x3view_destruct(inEvent, data);
+ break;
+ }
+ break;
+ case kEventClassControl:
+ switch (eventKind) {
+ case kEventControlInitialize:
+ err = noErr;
+ break;
+ case kEventControlDraw:
+ err = x3view_draw(inEvent, data);
+ break;
+ case kEventControlGetData:
+ err = x3view_get_data(inEvent, data);
+ break;
+ case kEventControlSetData:
+ err = x3view_set_data(inEvent, data);
+ break;
+ case kEventControlTrack:
+ err = x3view_track(inEvent, data);
+ break;
+ case kEventControlHitTest:
+ err = x3view_hittest(inEvent, data);
+ break;
+ case kEventControlClick:
+ printf("click event\n");
+ break;
+ /*...*/
+ }
+ break;
+ }
+ return err;
+}
+
+static OSStatus
+x3view_register(void)
+{
+ OSStatus err = noErr;
+ static HIObjectClassRef x3view_ClassRef = NULL;
+
+ if (x3view_ClassRef == NULL) {
+ EventTypeSpec eventList[] = {
+ { kEventClassHIObject, kEventHIObjectConstruct },
+ { kEventClassHIObject, kEventHIObjectInitialize },
+ { kEventClassHIObject, kEventHIObjectDestruct },
+
+ { kEventClassControl, kEventControlActivate },
+ { kEventClassControl, kEventControlDeactivate },
+ { kEventClassControl, kEventControlDraw },
+ { kEventClassControl, kEventControlHiliteChanged },
+ { kEventClassControl, kEventControlHitTest },
+ { kEventClassControl, kEventControlInitialize },
+ { kEventClassControl, kEventControlGetData },
+ { kEventClassControl, kEventControlSetData },
+ { kEventClassControl, kEventControlTrack },
+ { kEventClassControl, kEventControlClick }
+ };
+ err = HIObjectRegisterSubclass(kX3ViewClassID,
+ kHIViewClassID,
+ 0,
+ x3view_handler,
+ GetEventTypeCount(eventList),
+ eventList,
+ NULL,
+ &x3view_ClassRef);
+ }
+ return err;
+}
+
+OSStatus x3view_create(
+ WindowRef inWindow,
+ const HIRect* inBounds,
+ HIViewRef* outView)
+{
+ OSStatus err;
+ EventRef event;
+
+ err = x3view_register();
+ require_noerr(err, CantRegister);
+
+ err = CreateEvent(NULL, kEventClassHIObject, kEventHIObjectInitialize,
+ GetCurrentEventTime(), 0, &event);
+ require_noerr(err, CantCreateEvent);
+
+ if (inBounds != NULL) {
+ err = SetEventParameter(event, 'Boun', typeHIRect, sizeof(HIRect),
+ inBounds);
+ require_noerr(err, CantSetParameter);
+ }
+
+ err = HIObjectCreate(kX3ViewClassID, event, (HIObjectRef*)outView);
+ require_noerr(err, CantCreate);
+
+ if (inWindow != NULL) {
+ HIViewRef root;
+ err = GetRootControl(inWindow, &root);
+ require_noerr(err, CantGetRootView);
+ err = HIViewAddSubview(root, *outView);
+ }
+ CantCreate:
+ CantGetRootView:
+ CantSetParameter:
+ CantCreateEvent:
+ ReleaseEvent(event);
+ CantRegister:
+ return err;
+}
+
+x3widget *x3view(x3widget *parent, x3viewflags flags, x3viewclient *vc)
+{
+ WindowRef window = x3window_of(parent);
+ HIRect r = { {10, 10}, {30, 100} };
+ OSStatus err;
+ HIViewRef view;
+
+ err = x3view_create(window, &r, &view);
+ err = SetControlData(view, 1, 'X3vc', sizeof(x3view_data *), &vc);
+ err = SetControlData(view, 1, 'X3vf', sizeof(x3viewflags), &flags);
+ HIViewSetVisible(view, true);
+ return x3widget_new_hiview(parent, NULL, &x3buttontype, view);
+}
+
+void
+x3view_dirty(x3widget *w)
+{
+ if (w->var == x3carbonhiview)
+ HIViewSetNeedsDisplay(w->u.hiview, true);
+}
+
+void x3view_scrollto(x3widget *w, int x, int y, int width, int height)
+{
+ /* todo */
+}
+
+void x3viewclient_init(x3viewclient *vc)
+{
+ vc->destroy = NULL;
+ vc->mouse = NULL;
+ vc->key = NULL;
+ vc->draw = NULL;
+}
+
+/* Functions for manipulating widget state - some fairly polymorphic. */
+
+void x3setactive(x3widget *w, int active)
+{
+ if (w->var == x3carbonmenuitem) {
+ MenuRef menu = w->parent->u.menu;
+ if (active)
+ EnableMenuItem(menu, w->u.menuitem);
+ else
+ DisableMenuItem(menu, w->u.menuitem);
+ /* According to Carbon docs, we need to redraw menu. */
+ } else if (w->var == x3carbonhiview) {
+ if (active)
+ ActivateControl(w->u.hiview);
+ else
+ DeactivateControl(w->u.hiview);
+ }
+}
+
+int x3hasfocus(x3widget *w)
+{
+ if (w->var == x3carbonhiview) {
+ return HIViewSubtreeContainsFocus(w->u.hiview);
+ } else
+ return 0;
+}
+
+/* 2d drawing functions, implemented using Quartz */
+
+void
+x3moveto(x3dc *dc, double x, double y)
+{
+ if (dc->path == NULL) {
+ dc->path = CGPathCreateMutable();
+ }
+ CGPathMoveToPoint(dc->path, NULL, x, y);
+}
+
+void
+x3lineto(x3dc *dc, double x, double y)
+{
+ CGPathAddLineToPoint(dc->path, NULL, x, y);
+}
+
+void
+x3curveto(x3dc *dc,
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3)
+{
+ CGPathAddCurveToPoint(dc->path, NULL, x1, y1, x2, y2, x3, y3);
+}
+
+void
+x3closepath(x3dc *dc)
+{
+ CGPathCloseSubpath(dc->path);
+}
+
+void
+x3rectangle(x3dc *dc, double x, double y, double width, double height)
+{
+ CGRect rect;
+
+ if (dc->path == NULL) {
+ dc->path = CGPathCreateMutable();
+ }
+ rect.origin.x = x;
+ rect.origin.y = y;
+ rect.size.width = width;
+ rect.size.height = height;
+ CGPathAddRect(dc->path, NULL, rect);
+}
+
+void
+x3getcurrentpoint(x3dc *dc, double *px, double *py)
+{
+ CGPoint point = CGPathGetCurrentPoint(dc->path);
+
+ *px = point.x;
+ *py = point.y;
+}
+
+void
+x3setrgba(x3dc *dc, unsigned int rgba)
+{
+ CGContextSetRGBFillColor(dc->ctx,
+ ((rgba >> 24) & 0xff) * (1.0/255),
+ ((rgba >> 16) & 0xff) * (1.0/255),
+ ((rgba >> 8) & 0xff) * (1.0/255),
+ (rgba & 0xff) * (1.0/255));
+ CGContextSetRGBStrokeColor(dc->ctx,
+ ((rgba >> 24) & 0xff) * (1.0/255),
+ ((rgba >> 16) & 0xff) * (1.0/255),
+ ((rgba >> 8) & 0xff) * (1.0/255),
+ (rgba & 0xff) * (1.0/255));
+}
+
+void
+x3setlinewidth(x3dc *dc, double w)
+{
+ CGContextSetLineWidth(dc->ctx, w);
+}
+
+void
+x3fill(x3dc *dc)
+{
+ CGContextAddPath(dc->ctx, dc->path);
+ CGPathRelease(dc->path);
+ dc->path = NULL;
+ CGContextFillPath(dc->ctx);
+}
+
+void
+x3stroke(x3dc *dc)
+{
+ CGContextAddPath(dc->ctx, dc->path);
+ CGPathRelease(dc->path);
+ dc->path = NULL;
+ CGContextStrokePath(dc->ctx);
+}
+
+void
+x3selectfont(x3dc *dc, char *fontname, int slant, int weight)
+{
+ CGContextSelectFont(dc->ctx, fontname, 8.0, kCGEncodingMacRoman);
+}
+
+void
+x3setfontsize(x3dc *dc, double size)
+{
+ CGContextSetFontSize(dc->ctx, size);
+}
+
+void
+x3showtext(x3dc *dc, char *text)
+{
+ CGPoint point = CGPathGetCurrentPoint(dc->path);
+ CGAffineTransform textmat;
+
+ textmat = CGAffineTransformMakeScale(1, -1);
+ CGContextSetTextMatrix(dc->ctx, textmat);
+ CGContextShowTextAtPoint(dc->ctx, point.x, point.y, text, strlen(text));
+}
+
+void x3textextents(x3dc *dc, char *text, x3extents *extents)
+{
+ /* todo */
+}
diff --git a/third_party/spiro/x3/x3common.c b/third_party/spiro/x3/x3common.c
new file mode 100644
index 0000000..e4845ff
--- /dev/null
+++ b/third_party/spiro/x3/x3common.c
@@ -0,0 +1,359 @@
+/* Functions common to more than one platform. */
+
+#include <stdlib.h>
+
+#include "x3.h"
+#include "x3common.h"
+
+int n_x3needshow = 0;
+int n_x3needshow_max = 0;
+x3widget **x3needshow = NULL;
+
+#if defined(X3_GTK) || defined(X3_WIN32)
+int x3n_winopen = 0;
+#endif
+
+#if defined(X3_CARBON) || defined(X3_WIN32)
+
+int n_x3needsizereqs = 0;
+int n_x3needsizereqs_max = 0;
+x3widget **x3needsizereqs = NULL;
+
+int n_x3needsizeallocs = 0;
+int n_x3needsizeallocs_max = 0;
+x3widget **x3needsizeallocs = NULL;
+
+void x3qsizereq(x3widget *w)
+{
+ if (w && !(w->flags & x3flag_needsizereq)) {
+ x3qsizereq(w->parent);
+ if (n_x3needsizereqs == n_x3needsizereqs_max)
+ x3needsizereqs = (x3widget **)realloc(x3needsizereqs,
+ sizeof(x3widget *) *
+ (n_x3needsizereqs_max <<= 1));
+ x3needsizereqs[n_x3needsizereqs++] = w;
+ w->flags |= x3flag_needsizereq;
+ }
+}
+
+void x3add_default(x3widget *parent, x3widget *child)
+{
+ const int n_children_init = 4;
+
+ child->parent = parent;
+ if (parent->n_children == 0)
+ parent->children = (x3widget **)malloc(sizeof(x3widget *) *
+ n_children_init);
+ else if (parent->n_children >= n_children_init &&
+ !(parent->n_children & (parent->n_children - 1)))
+ parent->children = (x3widget **)realloc(parent->children,
+ sizeof(x3widget *) *
+ (parent->n_children << 1));
+ parent->children[parent->n_children++] = child;
+}
+
+void x3add(x3widget *parent, x3widget *child)
+{
+ parent->type->add(parent, child);
+}
+
+/* Widgets common to carbon and win32 platforms */
+
+typedef struct {
+ x3widget base;
+ int homogeneous;
+ int spacing;
+ int cur_child_prop;
+ int *child_props; /* bit 0=expand, bit 1=fill, bits 2:31=padding */
+} x3widget_box;
+
+void x3vbox_sizereq(x3widget *w)
+{
+ x3widget_box *wb = (x3widget_box *)w;
+ int i;
+ int spacing = wb->spacing;
+
+ w->sizerequest.x0 = 0;
+ w->sizerequest.y0 = 0;
+ w->sizerequest.x1 = 0;
+ w->sizerequest.y1 = 0;
+ for (i = 0; i < w->n_children; i++) {
+ x3widget *child = w->children[i];
+ int childw = child->sizerequest.x1 - child->sizerequest.x0;
+ int childh = child->sizerequest.y1 - child->sizerequest.y0;
+ int padding = wb->child_props[i] >> 2;
+
+ if (i < w->n_children - 1)
+ childh += spacing;
+ w->sizerequest.y1 += childh + 2 * padding;
+ if (childw > w->sizerequest.x1)
+ w->sizerequest.x1 = childw;
+ }
+}
+
+void x3vbox_sizealloc(x3widget *w, x3rect *r)
+{
+ x3widget_box *wb = (x3widget_box *)w;
+ int i;
+ x3rect child_r = *r;
+ int spacing = wb->spacing;
+ int n_extend = 0;
+ int n_stretch, i_stretch = 0;
+ int extra;
+
+ /* todo: impl padding & homog, factor hbox/vbox common */
+ printf("vbox sizealloc = (%g, %g) - (%g, %g), req was %g x %g\n",
+ r->x0, r->y0, r->x1, r->y1,
+ w->sizerequest.x1, w->sizerequest.y1);
+ extra = r->y1 - r->y0 - w->sizerequest.y1;
+ for (i = 0; i < w->n_children; i++)
+ if (wb->child_props[i] & 1)
+ n_extend++;
+ n_stretch = n_extend ? n_extend : w->n_children;
+ printf("extra = %d, n_stretch = %d\n", extra, n_stretch);
+ for (i = 0; i < w->n_children; i++) {
+ x3widget *child = w->children[i];
+ int childh = child->sizerequest.y1 - child->sizerequest.y0;
+ int my_extra;
+ int next_top;
+
+ if (n_extend == 0 || (wb->child_props[i] & 1)) {
+ my_extra = (extra * (i_stretch + 1)) / n_stretch -
+ (extra * i_stretch) / n_stretch;
+ i_stretch++;
+ } else
+ my_extra = 0;
+ next_top = child_r.y0 + childh + spacing + my_extra;
+
+ if (wb->child_props[i] & 2) {
+ childh += my_extra;
+ } else {
+ child_r.y0 += my_extra >> 1;
+ }
+ child_r.y1 = child_r.y0 + childh;
+
+ child->type->sizealloc(child, &child_r);
+ child->flags &= ~x3flag_needsizealloc;
+
+ child_r.y0 = next_top;
+ }
+}
+
+void x3vbox_add(x3widget *w, x3widget *child)
+{
+ x3widget_box *wb = (x3widget_box *)w;
+ const int n_children_init = 4;
+
+ if (w->n_children == 0)
+ wb->child_props = (int *)malloc(sizeof(int) * n_children_init);
+ else if (w->n_children >= n_children_init &&
+ !(w->n_children & (w->n_children - 1)))
+ wb->child_props = (int *)realloc(wb->child_props,
+ sizeof(int) * (w->n_children << 1));
+ wb->child_props[w->n_children] = wb->cur_child_prop;
+ x3add_default(w, child);
+}
+
+x3type x3vboxtype = { x3vbox_sizereq,
+ x3vbox_sizealloc,
+ x3vbox_add };
+
+x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing)
+{
+ x3widget_box *result = (x3widget_box *)malloc(sizeof(x3widget_box));
+ x3widget_init(&result->base, &x3vboxtype);
+ x3add(parent, &result->base);
+ result->homogeneous = homogeneous;
+ result->spacing = spacing;
+ result->cur_child_prop = 3;
+ x3qsizereq(&result->base);
+ return &result->base;
+}
+
+void x3setpacking(x3widget *w, int fill, int expand, int padding)
+{
+ if (w->type == &x3vboxtype) {
+ x3widget_box *wb = (x3widget_box *)w;
+ int child_props = padding << 2;
+
+ if (fill) child_props |= 1;
+ if (expand) child_props |= 2;
+ wb->cur_child_prop = child_props;
+ }
+}
+
+typedef struct {
+ x3widget base;
+ x3alignment alignment;
+} x3widget_align;
+
+void x3align_sizereq(x3widget *w)
+{
+ w->sizerequest.x0 = 0;
+ w->sizerequest.y0 = 0;
+ w->sizerequest.x1 = 0;
+ w->sizerequest.y1 = 0;
+ if (w->n_children) {
+ x3widget *child = w->children[0];
+ int childw = child->sizerequest.x1 - child->sizerequest.x0;
+ int childh = child->sizerequest.y1 - child->sizerequest.y0;
+ w->sizerequest.x1 = childw;
+ w->sizerequest.y1 = childh;
+ }
+}
+
+void x3align_sizealloc(x3widget *w, x3rect *r)
+{
+ x3widget_align *z = (x3widget_align *)w;
+ x3alignment a = z->alignment;
+ int xa = a & 3;
+ int ya = (a >> 2) & 3;
+ x3rect child_r = *r;
+
+ printf("align sizealloc = (%g, %g) - (%g, %g)\n",
+ r->x0, r->y0, r->x1, r->y1);
+ if (w->n_children) {
+ x3widget *child = w->children[0];
+ if (xa < 3) {
+ int childw = child->sizerequest.x1 - child->sizerequest.x0;
+ int pad = r->x1 - r->x0 - childw;
+ child_r.x0 += (pad * (1 + (xa >> 1) - (xa & 1)) + 1) >> 1;
+ child_r.x1 = child_r.x0 + childw;
+ }
+ if (ya < 3) {
+ int childh = child->sizerequest.y1 - child->sizerequest.y0;
+ int pad = r->y1 - r->y0 - childh;
+ child_r.y0 += (pad * (1 + (ya >> 1) - (ya & 1)) + 1) >> 1;
+ child_r.y1 = child_r.y0 + childh;
+ }
+
+ child->type->sizealloc(child, &child_r);
+ child->flags &= ~x3flag_needsizealloc;
+ }
+}
+
+x3type x3aligntype = { x3align_sizereq,
+ x3align_sizealloc,
+ x3add_default };
+
+x3widget *x3align(x3widget *parent, x3alignment alignment)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget_align));
+ x3widget_init(result, &x3aligntype);
+ x3add(parent, result);
+ x3qsizereq(result);
+ ((x3widget_align *)result)->alignment = alignment;
+ return result;
+}
+
+typedef struct {
+ x3widget base;
+ int t, b, l, r;
+} x3widget_pad;
+
+void x3pad_sizereq(x3widget *w)
+{
+ x3widget_pad *z = (x3widget_pad *)w;
+ w->sizerequest.x0 = 0;
+ w->sizerequest.y0 = 0;
+ w->sizerequest.x1 = 0;
+ w->sizerequest.y1 = 0;
+ if (w->n_children) {
+ x3widget *child = w->children[0];
+ int childw = child->sizerequest.x1 - child->sizerequest.x0;
+ int childh = child->sizerequest.y1 - child->sizerequest.y0;
+ w->sizerequest.x1 = childw + z->l + z->r;
+ w->sizerequest.y1 = childh + z->t + z->b;
+ }
+}
+
+void x3pad_sizealloc(x3widget *w, x3rect *r)
+{
+ x3widget_pad *z = (x3widget_pad *)w;
+ x3rect child_r = *r;
+
+ printf("pad sizealloc = (%g, %g) - (%g, %g)\n",
+ r->x0, r->y0, r->x1, r->y1);
+ if (w->n_children) {
+ x3widget *child = w->children[0];
+ child_r.x0 += z->l;
+ child_r.x1 -= z->r;
+ child_r.y0 += z->t;
+ child_r.y1 -= z->b;
+
+ child->type->sizealloc(child, &child_r);
+ child->flags &= ~x3flag_needsizealloc;
+ }
+}
+
+x3type x3padtype = { x3pad_sizereq,
+ x3pad_sizealloc,
+ x3add_default };
+
+x3widget *x3pad(x3widget *parent, int t, int b, int l, int r)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget_pad));
+ x3widget_init(result, &x3padtype);
+ x3add(parent, result);
+ x3qsizereq(result);
+ ((x3widget_pad *)result)->t = t;
+ ((x3widget_pad *)result)->b = b;
+ ((x3widget_pad *)result)->l = l;
+ ((x3widget_pad *)result)->r = r;
+ return result;
+}
+
+#endif
+
+void x3initqs(void)
+{
+ n_x3needshow = 0;
+ x3needshow = (x3widget **)malloc(sizeof(x3widget *) *
+ (n_x3needshow_max = 16));
+
+#if defined(X3_CARBON) || defined(X3_WIN32)
+ n_x3needsizereqs = 0;
+ x3needsizereqs = (x3widget **)malloc(sizeof(x3widget *) *
+ (n_x3needsizereqs_max = 16));
+
+ n_x3needsizeallocs = 0;
+ x3needsizeallocs = (x3widget **)malloc(sizeof(x3widget *) *
+ (n_x3needsizeallocs_max = 16));
+#endif
+}
+
+void x3qshow(x3widget *w)
+{
+ if (n_x3needshow == n_x3needshow_max)
+ x3needshow = (x3widget **)realloc(x3needshow,
+ sizeof(x3widget *) *
+ (n_x3needshow_max <<= 1));
+ x3needshow[n_x3needshow++] = w;
+}
+
+void x3sync(void)
+{
+ int i;
+
+#if defined(X3_CARBON) || defined(X3_WIN32)
+
+ for (i = n_x3needsizereqs - 1; i >= 0; i--) {
+ x3widget *w = x3needsizereqs[i];
+ w->type->sizereq(w);
+ w->flags &= ~x3flag_needsizereq;
+ w->flags |= x3flag_needsizealloc;
+ }
+ for (i = 0; i < n_x3needsizereqs; i++) {
+ x3widget *w = x3needsizereqs[i];
+ if (w->flags & x3flag_needsizealloc) {
+ w->type->sizealloc(w, NULL);
+ w->flags &= ~x3flag_needsizealloc;
+ }
+ }
+ n_x3needsizereqs = 0;
+#endif
+
+ for (i = 0; i < n_x3needshow; i++)
+ x3_window_show(x3needshow[i]);
+ n_x3needshow = 0;
+}
diff --git a/third_party/spiro/x3/x3common.h b/third_party/spiro/x3/x3common.h
new file mode 100644
index 0000000..fc616a2
--- /dev/null
+++ b/third_party/spiro/x3/x3common.h
@@ -0,0 +1,16 @@
+#if defined(X3_CARBON) || defined(X3_WIN32)
+
+void x3qsizereq(x3widget *w);
+
+void x3add_default(x3widget *parent, x3widget *child);
+void x3add(x3widget *parent, x3widget *child);
+
+#endif
+
+void x3initqs(void);
+void x3qshow(x3widget *w);
+void x3sync(void);
+
+
+/* provided by impls to common routines */
+void x3_window_show(x3widget *w);
diff --git a/third_party/spiro/x3/x3gtk.c b/third_party/spiro/x3/x3gtk.c
new file mode 100644
index 0000000..162f496
--- /dev/null
+++ b/third_party/spiro/x3/x3gtk.c
@@ -0,0 +1,778 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "x3.h"
+#include "x3common.h"
+
+void x3init(int *pargc, char ***pargv)
+{
+ gtk_init(pargc, pargv);
+ x3initqs();
+}
+
+static void
+x3_getfirst_callback(GtkWidget *widget, gpointer data)
+{
+ GtkWidget **pwidget = (GtkWidget **)data;
+
+ if (*pwidget == NULL)
+ *pwidget = widget;
+}
+
+static GtkWidget *x3_gtkwidget_getchild(GtkWidget *w)
+{
+ GtkWidget *child = NULL;
+ gtk_container_foreach(GTK_CONTAINER(w),
+ x3_getfirst_callback,
+ (gpointer)&child);
+ return child;
+}
+
+typedef struct {
+ x3widget base;
+ gboolean expand;
+ gboolean fill;
+ guint padding;
+} x3widget_box;
+
+static void x3widget_init(x3widget *w, x3widget *parent, char *name,
+ GtkWidget *widget)
+{
+ w->name = g_strdup(name);
+ w->widget = widget;
+ w->parent = parent;
+ if (parent) {
+ if (GTK_IS_WINDOW(parent->widget)) {
+ GtkWidget *vbox = x3_gtkwidget_getchild(parent->widget);
+
+ if (GTK_IS_MENU_ITEM(widget)) {
+ GtkWidget *first_child = x3_gtkwidget_getchild(vbox);
+ GtkWidget *menubar;
+
+ if (first_child == NULL || !GTK_IS_MENU_BAR(first_child)) {
+ menubar = gtk_menu_bar_new();
+ gtk_box_pack_start(GTK_BOX(vbox), menubar,
+ FALSE, FALSE, 0);
+ gtk_widget_show(menubar);
+ } else
+ menubar = first_child;
+ gtk_menu_bar_append(GTK_MENU_BAR(menubar), widget);
+ } else {
+ gtk_container_add(GTK_CONTAINER(vbox), widget);
+ }
+ } else if (GTK_IS_MENU_ITEM(parent->widget)) {
+ GtkWidget *menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(parent->widget));
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), widget);
+ } else if (GTK_IS_BOX(parent->widget)) {
+ x3widget_box *pwb = (x3widget_box *)parent;
+ gtk_box_pack_start(GTK_BOX(parent->widget), widget,
+ pwb->expand, pwb->fill, pwb->padding);
+ } else {
+ gtk_container_add(GTK_CONTAINER(parent->widget), widget);
+ }
+ }
+}
+
+static x3widget *x3widget_new(x3widget *parent, char *name, GtkWidget *widget)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+
+ x3widget_init(result, parent, name, widget);
+ return result;
+}
+
+static x3widget *x3box_new(x3widget *parent, GtkWidget *widget)
+{
+ x3widget_box *result = (x3widget_box *)malloc(sizeof(x3widget_box));
+
+ x3widget_init(&result->base, parent, NULL, widget);
+ result->expand = TRUE;
+ result->fill = TRUE;
+ result->padding = 0;
+ return &result->base;
+}
+
+void x3_window_show(x3widget *w)
+{
+ gtk_widget_show(w->widget);
+}
+
+void x3window_setdefaultsize(x3widget *w, int width, int height)
+{
+ gtk_window_set_default_size(GTK_WINDOW(w->widget), width, height);
+}
+
+void x3main(void)
+{
+ x3sync();
+ gtk_main();
+}
+
+/* some constructors */
+
+typedef struct {
+ x3widget base;
+ x3window_callback callback;
+ void *callback_data;
+
+ GtkAccelGroup *accel_group;
+} x3widget_window;
+
+gboolean x3window_delete(GtkWidget *window, GdkEvent *event, gpointer data)
+{
+ /* todo: pass this as a command callback */
+ if (--x3n_winopen <= 0)
+ gtk_main_quit();
+ return FALSE;
+}
+
+x3widget *x3window(x3windowflags flags, char *label,
+ x3window_callback callback, void *callback_data)
+{
+ GtkWidget *window;
+ GtkWidget *vbox;
+ x3widget_window *result;
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(window), label);
+ vbox = gtk_vbox_new(FALSE, 0);
+ gtk_widget_show(vbox);
+ gtk_container_add(GTK_CONTAINER(window), vbox);
+ result = (x3widget_window *)malloc(sizeof(x3widget_window));
+ x3widget_init(&result->base, NULL, "mainwin", window);
+ result->callback = callback;
+ result->callback_data = callback_data;
+ result->accel_group = gtk_accel_group_new();
+ gtk_window_add_accel_group(GTK_WINDOW(window), result->accel_group);
+ g_signal_connect(G_OBJECT(window), "delete-event",
+ G_CALLBACK(x3window_delete), result);
+ x3qshow(&result->base);
+ x3n_winopen++;
+ return &result->base;
+}
+
+x3widget *x3menu(x3widget *parent, char *name)
+{
+ GtkWidget *item;
+ GtkWidget *menu;
+
+ menu = gtk_menu_new();
+
+ item = gtk_menu_item_new_with_label(name);
+ gtk_widget_show(item);
+
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu);
+ return x3widget_new(parent, NULL, item);
+}
+
+typedef struct {
+ x3widget base;
+ char *cmd;
+} x3widget_cmdable;
+
+static void x3doevent(x3widget_cmdable *wc, char *str)
+{
+ char *cmd = wc->cmd;
+ x3widget *w = &wc->base;
+ x3widget_window *ww;
+
+ while (w->parent) w = w->parent;
+ ww = (x3widget_window *)w;
+ ww->callback(w, ww->callback_data, cmd, str, NULL, NULL);
+ x3sync();
+}
+
+static void x3cmdable_clicked(GtkWidget *widget, gpointer data)
+{
+ x3widget_cmdable *wc = (x3widget_cmdable *)data;
+
+ x3doevent(wc, "command");
+}
+
+static const char *asciinames[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ "space",
+ "exclam",
+ "quotedbl",
+ "numbersign",
+ "dollar",
+ "percent",
+ "ampersand",
+ "apostrophe",
+ "parenleft",
+ "parenright",
+ "asterisk",
+ "plus",
+ "comma",
+ "minus",
+ "period",
+ "slash",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "colon",
+ "semicolon",
+ "less",
+ "equal",
+ "greater",
+ "question",
+ "at",
+ "<shift>a",
+ "<shift>b",
+ "<shift>c",
+ "<shift>d",
+ "<shift>e",
+ "<shift>f",
+ "<shift>g",
+ "<shift>h",
+ "<shift>i",
+ "<shift>j",
+ "<shift>k",
+ "<shift>l",
+ "<shift>m",
+ "<shift>n",
+ "<shift>o",
+ "<shift>p",
+ "<shift>q",
+ "<shift>r",
+ "<shift>s",
+ "<shift>t",
+ "<shift>u",
+ "<shift>v",
+ "<shift>w",
+ "<shift>x",
+ "<shift>y",
+ "<shift>z",
+ "bracketleft",
+ "backslash",
+ "bracketright",
+ "asciicircum",
+ "underscore",
+ "grave",
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "braceleft",
+ "bar",
+ "braceright",
+ "asciitilde"
+};
+
+/* return 1 on success */
+static int
+x3parseshortcut(const char *shortcut,
+ guint *accelerator_key, GdkModifierType *accelerator_mods)
+{
+ int len;
+ char tmp[256];
+ int i;
+
+ if (shortcut == NULL) return 0;
+ len = strlen(shortcut);
+ if (len >= sizeof(tmp) - 1) return 0;
+ strcpy(tmp, shortcut);
+ for (i = 0; i < len - 5; i++)
+ if (!memcmp(tmp + i, "<cmd>", 5))
+ memcpy(tmp + i, "<ctl>", 5);
+ if (len == 1 || tmp[len - 2] == '>') {
+ unsigned char c = (unsigned char)tmp[len-1];
+ if (c < sizeof(asciinames) / sizeof(asciinames[0]) && asciinames[c] &&
+ len + strlen(asciinames[c]) < sizeof(tmp))
+ strcpy(tmp + len - 1, asciinames[c]);
+ }
+ gtk_accelerator_parse(tmp, accelerator_key, accelerator_mods);
+ return *accelerator_key != 0 || *accelerator_mods != 0;
+}
+
+static GtkAccelGroup *
+x3getaccelgroup(x3widget *w)
+{
+ while (w->parent) w = w->parent;
+ if (!GTK_IS_WINDOW(w->widget)) return NULL;
+ return ((x3widget_window *)w)->accel_group;
+}
+
+x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut)
+{
+ GtkWidget *item;
+ x3widget_cmdable *result = (x3widget_cmdable *)malloc(sizeof(x3widget_cmdable));
+ guint accel_key;
+ GdkModifierType accel_mods;
+
+ item = gtk_menu_item_new_with_label(name);
+ x3widget_init(&result->base, parent, cmd, item);
+ result->cmd = g_strdup(cmd);
+ g_signal_connect(G_OBJECT(item), "activate",
+ G_CALLBACK(x3cmdable_clicked), result);
+
+ if (x3parseshortcut(shortcut, &accel_key, &accel_mods)) {
+ gtk_widget_add_accelerator(item, "activate", x3getaccelgroup(parent),
+ accel_key, accel_mods, GTK_ACCEL_VISIBLE);
+ }
+
+ gtk_widget_show(item);
+ return &result->base;
+}
+
+x3widget *x3menusep(x3widget *parent)
+{
+ GtkWidget *item;
+
+ item = gtk_separator_menu_item_new();
+ gtk_widget_show(item);
+ return x3widget_new(parent, NULL, item);
+}
+
+x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing)
+{
+ GtkWidget *vbox = gtk_vbox_new(homogeneous, spacing);
+
+ gtk_widget_show(vbox);
+ return x3box_new(parent, vbox);
+}
+
+x3widget *x3hpane(x3widget *parent)
+{
+ GtkWidget *hpane = gtk_hpaned_new();
+
+ gtk_widget_show(hpane);
+ return x3widget_new(parent, NULL, hpane);
+}
+
+x3widget *x3vpane(x3widget *parent)
+{
+ GtkWidget *vpane = gtk_vpaned_new();
+
+ gtk_widget_show(vpane);
+ return x3widget_new(parent, NULL, vpane);
+}
+
+x3widget *x3align(x3widget *parent, x3alignment alignment)
+{
+ int xa = alignment & 3;
+ int ya = (alignment >> 2) & 3;
+ float xalign = .5 * (1 + (xa >> 1) - (xa & 1));
+ float yalign = .5 * (1 + (ya >> 1) - (ya & 1));
+ float xscale = (xa == 3);
+ float yscale = (ya == 3);
+ GtkWidget *align = gtk_alignment_new(xalign, yalign, xscale, yscale);
+
+ gtk_widget_show(align);
+ return x3widget_new(parent, NULL, align);
+}
+
+x3widget *x3pad(x3widget *parent, int t, int b, int l, int r)
+{
+ GtkWidget *align = gtk_alignment_new(0, 0, 1, 1);
+
+ gtk_alignment_set_padding(GTK_ALIGNMENT(align), t, b, l, r);
+ gtk_widget_show(align);
+ return x3widget_new(parent, NULL, align);
+}
+
+x3widget *x3button(x3widget *parent, char *cmd, char *label)
+{
+ GtkWidget *button = gtk_button_new_with_label(label);
+ x3widget_cmdable *result = (x3widget_cmdable *)malloc(sizeof(x3widget_cmdable));
+
+ x3widget_init(&result->base, parent, cmd, button);
+ result->cmd = g_strdup(cmd);
+ g_signal_connect(G_OBJECT(button), "clicked",
+ G_CALLBACK(x3cmdable_clicked), result);
+
+ gtk_widget_show(button);
+ return &result->base;
+}
+
+x3widget *x3label(x3widget *parent, char *text)
+{
+ GtkWidget *label = gtk_label_new(text);
+
+ gtk_widget_show(label);
+ return x3widget_new(parent, NULL, label);
+}
+
+x3widget *x3edittext(x3widget *parent, char *cmd)
+{
+ GtkWidget *entry = gtk_entry_new();
+
+ gtk_widget_show(entry);
+ return x3widget_new(parent, cmd, entry);
+}
+
+typedef struct {
+ x3widget base;
+ x3viewflags flags;
+ x3viewclient *vc;
+} x3widget_view;
+
+static gboolean x3view_expose(GtkWidget *widget, GdkEventExpose *event,
+ gpointer data)
+{
+ x3widget_view *w = (x3widget_view *)data;
+ GdkWindow *window = GTK_IS_LAYOUT(widget) ?
+ GTK_LAYOUT(widget)->bin_window :
+ widget->window;
+
+ if (w->vc && w->vc->draw) {
+ x3dc dc;
+
+ dc.x = event->area.x;
+ dc.y = event->area.y;
+ dc.width = event->area.width;
+ dc.height = event->area.height;
+ if (w->flags & x3view_rgb) {
+ dc.rowstride = (event->area.width * 3 + 3) & -4;
+ dc.buf = (guchar *)malloc(event->area.height * dc.rowstride);
+ dc.cr = NULL;
+
+ w->vc->draw(w->vc, &dc);
+ gdk_draw_rgb_image(window, widget->style->black_gc,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height,
+ GDK_RGB_DITHER_NORMAL,
+ dc.buf, dc.rowstride);
+ free(dc.buf);
+ } else if (w->flags & x3view_2d) {
+ dc.cr = gdk_cairo_create(window);
+ dc.buf = NULL;
+
+ w->vc->draw(w->vc, &dc);
+ cairo_destroy(dc.cr);
+ }
+ }
+
+#if 1
+ /* experimental code for managing cairo dynamics */
+ if (event->count == 0)
+ gdk_flush();
+#endif
+
+ return TRUE;
+}
+
+static gboolean x3view_button_press(GtkWidget *widget, GdkEventButton *event,
+ gpointer data)
+{
+ x3widget_view *w = (x3widget_view *)data;
+ guint button = event->button;
+
+ if (event->type == GDK_BUTTON_RELEASE) button = -button;
+
+ if (w->vc && w->vc->mouse) {
+ w->vc->mouse(w->vc, button, event->state, event->x, event->y);
+ return TRUE;
+ }
+ x3sync();
+ return FALSE;
+}
+
+static gboolean x3view_pointer_motion(GtkWidget *widget, GdkEventButton *event,
+ gpointer data)
+{
+ x3widget_view *w = (x3widget_view *)data;
+
+ if (w->vc && w->vc->mouse) {
+ w->vc->mouse(w->vc, 0, event->state,
+ event->x, event->y);
+ return TRUE;
+ }
+ x3sync();
+ return FALSE;
+}
+
+static gboolean x3view_key_press(GtkWidget *widget, GdkEventKey *event,
+ gpointer data)
+{
+ x3widget_view *w = (x3widget_view *)data;
+
+ if (w->vc && w->vc->key)
+ return w->vc->key(w->vc, gdk_keyval_name(event->keyval),
+ event->state, event->keyval);
+ x3sync();
+ return FALSE;
+}
+
+x3widget *x3view(x3widget *parent, x3viewflags flags, x3viewclient *vc)
+{
+ GtkWidget *container;
+ GtkWidget *event_target;
+ GtkWidget *drawing_area;
+ x3widget_view *result = (x3widget_view *)malloc(sizeof(x3widget_view));
+ GdkEventMask eventmask = 0;
+
+ if (flags & x3view_scroll) {
+ container = gtk_scrolled_window_new(NULL, NULL);
+ drawing_area = gtk_layout_new(NULL, NULL);
+ event_target = drawing_area;
+ /* todo: more intelligent size requesting of view */
+ gtk_widget_set_size_request(drawing_area, 1500, 1500);
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(container),
+ drawing_area);
+ } else {
+ container = gtk_event_box_new();
+ drawing_area = gtk_drawing_area_new();
+ event_target = container;
+ gtk_container_add(GTK_CONTAINER(container), drawing_area);
+ }
+ gtk_widget_show(container);
+
+ if (flags & x3view_key) {
+ g_object_set(GTK_OBJECT(event_target), "can-focus", TRUE, NULL);
+ eventmask |= GDK_KEY_PRESS_MASK;
+ g_signal_connect(G_OBJECT(event_target), "key_press_event",
+ G_CALLBACK(x3view_key_press), result);
+ }
+ if (flags & x3view_click) {
+ eventmask |= GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK;
+ g_signal_connect(G_OBJECT(event_target), "button_press_event",
+ G_CALLBACK(x3view_button_press), result);
+ g_signal_connect(G_OBJECT(event_target), "button_release_event",
+ G_CALLBACK(x3view_button_press), result);
+ }
+ if (flags & x3view_hover) {
+ eventmask |= GDK_POINTER_MOTION_MASK;
+ g_signal_connect(G_OBJECT(event_target), "motion_notify_event",
+ G_CALLBACK(x3view_pointer_motion), result);
+ }
+ gtk_widget_add_events(event_target, eventmask);
+
+ g_signal_connect(G_OBJECT(drawing_area), "expose_event",
+ G_CALLBACK(x3view_expose), result);
+ gtk_widget_show(drawing_area);
+ if (flags & x3view_rgb)
+ gtk_widget_set_double_buffered(drawing_area, FALSE);
+
+ x3widget_init(&result->base, parent, NULL, container);
+ result->flags = flags;
+ result->vc = vc;
+ return &result->base;
+}
+
+void x3view_dirty(x3widget *w)
+{
+ gtk_widget_queue_draw(w->widget);
+}
+
+static void
+x3scrollto_adj(GtkAdjustment *adj, int v, int size)
+{
+ if (adj && v != -1) {
+ if (size >= adj->page_size) {
+ /* target is bigger than adj; center as best as possible */
+ gtk_adjustment_set_value(adj, v - 0.5 * (size - adj->page_size));
+ } else if (adj->value > v) {
+ gtk_adjustment_set_value(adj, v);
+ } else if (adj->value + adj->page_size < v + size) {
+ gtk_adjustment_set_value(adj, v + size - adj->page_size);
+ }
+ }
+}
+
+void x3view_scrollto(x3widget *w, int x, int y, int width, int height)
+{
+ if (GTK_IS_SCROLLED_WINDOW(w->widget)) {
+ GtkScrolledWindow *sw = GTK_SCROLLED_WINDOW(w->widget);
+ x3scrollto_adj(gtk_scrolled_window_get_hadjustment(sw), x, width);
+ x3scrollto_adj(gtk_scrolled_window_get_vadjustment(sw), y, height);
+ }
+}
+
+void x3viewclient_init(x3viewclient *vc)
+{
+ vc->destroy = NULL;
+ vc->mouse = NULL;
+ vc->key = NULL;
+ vc->draw = NULL;
+}
+
+/* An argument can be made against the "fill" flag, because the same effect
+ as turning off fill can be achieved with the align widget. */
+void x3setpacking(x3widget *w, int fill, int expand, int padding)
+{
+ if (GTK_IS_BOX(w->widget)) {
+ x3widget_box *wb= (x3widget_box *)w;
+ wb->fill = fill;
+ wb->expand = expand;
+ wb->padding = padding;
+ }
+}
+
+typedef struct {
+ GtkWidget *parent;
+ int resize[2];
+ int shrink[2];
+ int i;
+} x3pane_setsizing_ctx;
+
+static void
+x3pane_setsizing_callback(GtkWidget *child, gpointer data)
+{
+ x3pane_setsizing_ctx *ctx = (x3pane_setsizing_ctx *)data;
+ gtk_container_child_set(GTK_CONTAINER(ctx->parent),
+ child,
+ "resize", ctx->resize[ctx->i],
+ "shrink", ctx->shrink[ctx->i],
+ NULL);
+ ctx->i++;
+}
+
+/* This implementation only works if the sizing is set _after_ the
+ * children are added. It wouldn't be too hard to fix, by putting the
+ * info in the pane's x3widget struct. */
+void x3pane_setsizing(x3widget *w, int child1_resize, int child1_shrink,
+ int child2_resize, int child2_shrink)
+{
+ x3pane_setsizing_ctx ctx;
+
+ ctx.parent = w->widget;
+ ctx.resize[0] = child1_resize;
+ ctx.shrink[0] = child1_shrink;
+ ctx.resize[1] = child2_resize;
+ ctx.shrink[1] = child2_shrink;
+ ctx.i = 0;
+ if (GTK_IS_PANED(w->widget)) {
+ gtk_container_foreach(GTK_CONTAINER(w->widget),
+ x3pane_setsizing_callback,
+ (gpointer)&ctx);
+ }
+}
+
+
+void x3setactive(x3widget *w, int active)
+{
+ gtk_widget_set_sensitive(w->widget, active != 0);
+}
+
+int x3hasfocus(x3widget *w)
+{
+ GtkWidget *widget = w->widget;
+ while (GTK_IS_CONTAINER(widget) && !GTK_IS_LAYOUT(widget))
+ widget = x3_gtkwidget_getchild(widget);
+ return GTK_WIDGET_HAS_FOCUS(widget);
+}
+
+/* 2d drawing functions, implemented using cairo */
+
+void
+x3moveto(x3dc *dc, double x, double y)
+{
+ cairo_move_to(dc->cr, x, y);
+}
+
+void
+x3lineto(x3dc *dc, double x, double y)
+{
+ cairo_line_to(dc->cr, x, y);
+}
+
+void
+x3curveto(x3dc *dc,
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3)
+{
+ cairo_curve_to(dc->cr, x1, y1, x2, y2, x3, y3);
+}
+
+void
+x3closepath(x3dc *dc)
+{
+ cairo_close_path(dc->cr);
+}
+
+void
+x3rectangle(x3dc *dc, double x, double y, double width, double height)
+{
+ cairo_rectangle(dc->cr, x, y, width, height);
+}
+
+void
+x3getcurrentpoint(x3dc *dc, double *px, double *py)
+{
+ cairo_get_current_point(dc->cr, px, py);
+}
+
+void
+x3setrgba(x3dc *dc, unsigned int rgba)
+{
+ cairo_set_source_rgba(dc->cr,
+ ((rgba >> 24) & 0xff) * (1.0/255),
+ ((rgba >> 16) & 0xff) * (1.0/255),
+ ((rgba >> 8) & 0xff) * (1.0/255),
+ (rgba & 0xff) * (1.0/255));
+}
+
+void
+x3setlinewidth(x3dc *dc, double w)
+{
+ cairo_set_line_width(dc->cr, w);
+}
+
+void
+x3fill(x3dc *dc)
+{
+ cairo_fill(dc->cr);
+}
+
+void
+x3stroke(x3dc *dc)
+{
+ cairo_stroke(dc->cr);
+}
+
+void
+x3selectfont(x3dc *dc, char *fontname, int slant, int weight)
+{
+ cairo_select_font_face(dc->cr, fontname, slant, weight);
+}
+
+void
+x3setfontsize(x3dc *dc, double size)
+{
+ cairo_set_font_size(dc->cr, size);
+}
+
+void
+x3showtext(x3dc *dc, char *text)
+{
+ cairo_show_text(dc->cr, text);
+}
+
+void
+x3textextents(x3dc *dc, char *text, x3extents *extents)
+{
+ cairo_text_extents(dc->cr, text, extents);
+}
diff --git a/third_party/spiro/x3/x3win32.c b/third_party/spiro/x3/x3win32.c
new file mode 100644
index 0000000..45e7d3c
--- /dev/null
+++ b/third_party/spiro/x3/x3win32.c
@@ -0,0 +1,174 @@
+#include "x3.h"
+#include "x3common.h"
+#include <stdio.h> /* for printf only, probably remove in production */
+
+HINSTANCE theInstance = NULL;
+
+void x3init_win32(HINSTANCE hInstance)
+{
+ theInstance = hInstance;
+}
+
+void x3widget_init(x3widget *w, const x3type *type)
+{
+ w->type = type;
+ w->name = NULL;
+ w->parent = NULL;
+ w->var = x3winnone;
+ w->u.hwnd = NULL;
+ w->n_children = 0;
+ w->children = NULL;
+}
+
+LRESULT CALLBACK x3WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (iMsg) {
+ case WM_DESTROY:
+ if (--x3n_winopen <= 0) {
+ PostQuitMessage(0);
+ return 0;
+ }
+ break;
+ }
+ return DefWindowProc(hwnd, iMsg, wParam, lParam);
+}
+
+void x3window_sizereq(x3widget *w)
+{
+}
+
+void x3window_sizealloc(x3widget *w, x3rect *r)
+{
+ int i;
+ RECT rect;
+ x3rect child_r;
+
+ GetClientRect(w->u.hwnd, &rect);
+ child_r.x0 = 0;
+ child_r.x1 = rect.right - rect.left;
+ child_r.y0 = 0;
+ child_r.y1 = rect.bottom - rect.top;
+ printf("x3window_sizealloc (%ld, %ld) - (%ld, %ld)\n",
+ rect.left, rect.top, rect.right, rect.bottom);
+ for (i = 0; i < w->n_children; i++) {
+ x3widget *child = w->children[i];
+ if (child->type->sizealloc)
+ child->type->sizealloc(child, &child_r);
+ child->flags &= ~x3flag_needsizealloc;
+ }
+}
+
+x3type x3windowtype = { x3window_sizereq,
+ x3window_sizealloc,
+ x3add_default };
+
+x3widget *x3window(x3windowflags flags, char *label,
+ x3window_callback callback, void *data)
+{
+ HWND hwnd;
+ DWORD style = WS_OVERLAPPEDWINDOW;
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+ WNDCLASSEX wndclass;
+
+ wndclass.cbSize = sizeof(wndclass);
+ wndclass.style = CS_HREDRAW | CS_VREDRAW;
+ wndclass.lpfnWndProc = x3WndProc;
+ wndclass.cbClsExtra = 0;
+ wndclass.cbWndExtra = 0;
+ wndclass.hInstance = theInstance;
+ wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
+ wndclass.lpszMenuName = NULL;
+ wndclass.lpszClassName = "x3win";
+ wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
+
+ RegisterClassEx(&wndclass);
+
+ hwnd = CreateWindowEx(0, "x3win", "My window",
+ style, 100, 100, 300, 300,
+ NULL, NULL,
+ theInstance, NULL);
+ x3widget_init(result, &x3windowtype);
+ result->var = x3winhwnd;
+ result->u.hwnd = hwnd;
+ x3_nwinopen++;
+ //ShowWindow(hwnd, SW_SHOWNORMAL);
+ return result;
+
+}
+
+void x3_window_show(x3widget *w)
+{
+ ShowWindow(w->u.hwnd, SW_SHOW);
+}
+
+static HWND x3hwnd_of(x3widget *w)
+{
+ while (w->parent) w = w->parent;
+ return w->var == x3winhwnd ? w->u.hwnd : NULL;
+}
+
+static x3widget *x3widget_new_hwnd(x3widget *parent, char *name,
+ const x3type *type,
+ HWND hwnd)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+ x3widget_init(result, type);
+ result->name = name ? strdup(name) : NULL;
+ result->var = x3winhwnd;
+ result->u.hwnd = hwnd;
+ x3add(parent, result);
+ x3qsizereq(result);
+ return result;
+}
+
+void x3button_sizereq(x3widget *w)
+{
+ w->sizerequest.x0 = 0;
+ w->sizerequest.y0 = 0;
+ w->sizerequest.x1 = 100;
+ w->sizerequest.y1 = 20;
+#ifdef VERBOSE
+ printf("button sizereq = (%d, %d) - (%d, %d)\n",
+ w->sizerequest.x0, w->sizerequest.y0,
+ w->sizerequest.x1, w->sizerequest.y1);
+#endif
+}
+
+void x3button_sizealloc(x3widget *w, x3rect *r)
+{
+ printf("button sizealloc = (%g, %g) - (%g, %g)\n",
+ r->x0, r->y0, r->x1, r->y1);
+ if (w->var == x3winhwnd) {
+ SetWindowPos(w->u.hwnd, HWND_TOP,
+ r->x0, r->y0, r->x1 - r->x0, r->y1 - r->y0,
+ SWP_NOZORDER);
+ }
+}
+
+x3type x3buttontype = { x3button_sizereq,
+ x3button_sizealloc,
+ x3add_default };
+
+x3widget *x3button(x3widget *parent, char *cmd, char *label)
+{
+ HWND hwnd;
+
+ hwnd = CreateWindow("button", label,
+ WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+ 10, 10, 100, 20, x3hwnd_of(parent), NULL,
+ theInstance, NULL);
+ return x3widget_new_hwnd(parent, cmd, &x3buttontype, hwnd);
+}
+
+void x3main(void)
+{
+ MSG msg;
+
+ x3sync();
+ while (GetMessage(&msg, NULL, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+}