├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── README_OLD.txt ├── curves ├── band.py ├── bezfigs.py ├── bigmat.py ├── cloth_off.py ├── clothoid.py ├── cornu.py ├── euler-elastica.py ├── fromcubic.py ├── mecsolve.py ├── mvc.py ├── numintsynth.py ├── offset.py ├── pcorn.py ├── plot_solve_clothoid.py ├── poly3.py ├── polymat-bad.py ├── polymat.py └── tocubic.py ├── font ├── Makefile ├── blend.c ├── cut.py ├── mkblends.py ├── replace_class.py └── segment.c ├── ppedit ├── Makefile ├── Makefile_gtk1 ├── README ├── bezctx.c ├── bezctx.h ├── bezctx_hittest.c ├── bezctx_hittest.h ├── bezctx_intf.h ├── bezctx_libart.c ├── bezctx_libart.h ├── bezctx_ps.c ├── bezctx_ps.h ├── bezctx_quartz.c ├── bezctx_quartz.h ├── bezctx_x3.c ├── bezctx_x3.h ├── carbon_main.c ├── cornu.c ├── cornu.h ├── image.c ├── image.h ├── pe_view.c ├── pe_view.h ├── plate.c ├── plate.h ├── ppedit.c ├── ppedit_gtk1.c ├── sexp.c ├── sexp.h ├── spiro.c ├── spiro.h └── zmisc.h └── x3 ├── Makefile ├── pyrex ├── Makefile ├── beztest.py ├── main.py └── x3.pyx ├── test.c ├── x3.h ├── x3carbon.c ├── x3common.c ├── x3common.h ├── x3gtk.c └── x3win32.c /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Raph Levien 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spiro 2 | 3 | This repository contains development work for Spiro, an interpolating spline based on spirals. 4 | 5 | The Spiro splines are described in great detail in Raph Levien's [thesis](http://levien.com/phd/phd.html). 6 | 7 | The code in this repository is primarily for reference and historical purposes, as active 8 | development of a new spline continues at [spline.technology](https://spline.technology). 9 | 10 | ## License 11 | 12 | The code in this repository is licensed under the terms of the [Apache-2](LICENSE-APACHE) or 13 | [MIT](LICENSE-MIT) license, at your choice. 14 | 15 | The ideas in this repository are free for all to implement. Previously there 16 | were patents, but those are hereby passed into the public domain. 17 | 18 | Note that [libspiro](https://github.com/fontforge/libspiro), which was forked from this 19 | codebase before its relicensing, is still released under the GNU General Public License. 20 | -------------------------------------------------------------------------------- /README_OLD.txt: -------------------------------------------------------------------------------- 1 | Spiro release 0.01 2 | 4 May 2007 3 | Raph Levien 4 | 5 | This is a very rough release of the Spiro toolset that I've been using 6 | to create my fonts. The main program is ppedit, and there's a more 7 | detailed README in that directory. 8 | 9 | To build ppedit, do: 10 | 11 | cd ppedit 12 | make 13 | 14 | It requires Gtk2 and cairo. 15 | 16 | The curves/ subdirectory contains python utilities for manipulating 17 | curves, including a Bezier optimizer. 18 | 19 | The font/ subdirectory contains tools for segmenting scans and 20 | compositing them into classes, suitable for tracing over. (Note, 21 | however, that the gtk2 build of ppedit doesn't yet load background 22 | images for tracing). 23 | 24 | Always see http://levien.com/spiro/ for more updates. 25 | -------------------------------------------------------------------------------- /curves/band.py: -------------------------------------------------------------------------------- 1 | # A little solver for band-diagonal matrices. Based on NR Ch 2.4. 2 | 3 | from math import * 4 | 5 | from Numeric import * 6 | 7 | do_pivot = True 8 | 9 | def bandec(a, m1, m2): 10 | n, m = a.shape 11 | mm = m1 + m2 + 1 12 | if m != mm: 13 | raise ValueError('Array has width %d expected %d' % (m, mm)) 14 | al = zeros((n, m1), Float) 15 | indx = zeros(n, Int) 16 | 17 | for i in range(m1): 18 | l = m1 - i 19 | for j in range(l, mm): a[i, j - l] = a[i, j] 20 | for j in range(mm - l, mm): a[i, j] = 0 21 | 22 | d = 1. 23 | 24 | l = m1 25 | for k in range(n): 26 | dum = a[k, 0] 27 | pivot = k 28 | if l < n: l += 1 29 | if do_pivot: 30 | for j in range(k + 1, l): 31 | if abs(a[j, 0]) > abs(dum): 32 | dum = a[j, 0] 33 | pivot = j 34 | indx[k] = pivot 35 | if dum == 0.: a[k, 0] = 1e-20 36 | if pivot != k: 37 | d = -d 38 | for j in range(mm): 39 | tmp = a[k, j] 40 | a[k, j] = a[pivot, j] 41 | a[pivot, j] = tmp 42 | for i in range(k + 1, l): 43 | dum = a[i, 0] / a[k, 0] 44 | al[k, i - k - 1] = dum 45 | for j in range(1, mm): 46 | a[i, j - 1] = a[i, j] - dum * a[k, j] 47 | a[i, mm - 1] = 0. 48 | return al, indx, d 49 | 50 | def banbks(a, m1, m2, al, indx, b): 51 | n, m = a.shape 52 | mm = m1 + m2 + 1 53 | l = m1 54 | for k in range(n): 55 | i = indx[k] 56 | if i != k: 57 | tmp = b[k] 58 | b[k] = b[i] 59 | b[i] = tmp 60 | if l < n: l += 1 61 | for i in range(k + 1, l): 62 | b[i] -= al[k, i - k - 1] * b[k] 63 | l = 1 64 | for i in range(n - 1, -1, -1): 65 | dum = b[i] 66 | for k in range(1, l): 67 | dum -= a[i, k] * b[k + i] 68 | b[i] = dum / a[i, 0] 69 | if l < mm: l += 1 70 | 71 | if __name__ == '__main__': 72 | a = zeros((10, 3), Float) 73 | for i in range(10): 74 | a[i, 0] = 1 75 | a[i, 1] = 2 76 | a[i, 2] = 1 77 | print a 78 | al, indx, d = bandec(a, 1, 1) 79 | print a 80 | print al 81 | print indx 82 | b = zeros(10, Float) 83 | b[5] = 1 84 | banbks(a, 1, 1, al, indx, b) 85 | print b 86 | -------------------------------------------------------------------------------- /curves/bezfigs.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from math import * 3 | 4 | import fromcubic 5 | import tocubic 6 | 7 | import cornu 8 | 9 | def eps_prologue(x0, y0, x1, y1, draw_box = False): 10 | print '%!PS-Adobe-3.0 EPSF' 11 | print '%%BoundingBox:', x0, y0, x1, y1 12 | print '%%EndComments' 13 | print '%%EndProlog' 14 | print '%%Page: 1 1' 15 | if draw_box: 16 | print x0, y0, 'moveto', x0, y1, 'lineto', x1, y1, 'lineto', x1, y0, 'lineto closepath stroke' 17 | 18 | def eps_trailer(): 19 | print '%%EOF' 20 | 21 | def fit_cubic_superfast(z0, z1, arclen, th0, th1, aab): 22 | chord = hypot(z1[0] - z0[0], z1[1] - z0[1]) 23 | cth0, sth0 = cos(th0), sin(th0) 24 | cth1, sth1 = -cos(th1), -sin(th1) 25 | armlen = .66667 * arclen 26 | a = armlen * aab 27 | b = armlen - a 28 | bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), 29 | (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] 30 | return bz 31 | 32 | def fit_cubic(z0, z1, arclen, th_fn, fast, aabmin = 0, aabmax = 1.): 33 | chord = hypot(z1[0] - z0[0], z1[1] - z0[1]) 34 | if (arclen < 1.000001 * chord): 35 | return [z0, z1], 0 36 | th0 = th_fn(0) 37 | th1 = th_fn(arclen) 38 | imax = 4 39 | jmax = 10 40 | if fast: 41 | imax = 1 42 | jmax = 0 43 | for i in range(imax): 44 | for j in range(jmax + 1): 45 | if jmax == 0: 46 | aab = 0.5 * (aabmin + aabmax) 47 | else: 48 | aab = aabmin + (aabmax - aabmin) * j / jmax 49 | if fast == 2: 50 | bz = fit_cubic_superfast(z0, z1, arclen, th0, th1, aab) 51 | else: 52 | bz = tocubic.fit_cubic_arclen(z0, z1, arclen, th0, th1, aab) 53 | score = tocubic.measure_bz_rk4(bz, arclen, th_fn) 54 | print '% aab =', aab, 'score =', score 55 | sys.stdout.flush() 56 | if j == 0 or score < best_score: 57 | best_score = score 58 | best_aab = aab 59 | best_bz = bz 60 | daab = .06 * (aabmax - aabmin) 61 | aabmin = max(0, best_aab - daab) 62 | aabmax = min(1, best_aab + daab) 63 | print '%--- best_aab =', best_aab 64 | return best_bz, best_score 65 | 66 | def cornu_to_cubic(t0, t1, figno): 67 | if figno == 1: 68 | aabmin = 0 69 | aabmax = 0.4 70 | elif figno == 2: 71 | aabmin = 0.5 72 | aabmax = 1. 73 | else: 74 | aabmin = 0 75 | aabmax = 1. 76 | fast = 0 77 | if figno == 3: 78 | fast = 1 79 | elif figno == 4: 80 | fast = 2 81 | def th_fn(s): 82 | return (s + t0) ** 2 83 | y0, x0 = cornu.eval_cornu(t0) 84 | y1, x1 = cornu.eval_cornu(t1) 85 | bz, score = fit_cubic((x0, y0), (x1, y1), t1 - t0, th_fn, fast, aabmin, aabmax) 86 | return bz, score 87 | 88 | def plot_k_of_bz(bz): 89 | dbz = tocubic.bz_deriv(bz) 90 | ddbz = tocubic.bz_deriv(dbz) 91 | cmd = 'moveto' 92 | ss = [0] 93 | def arclength_deriv(x, ss): 94 | dx, dy = tocubic.bz_eval(dbz, x) 95 | return [hypot(dx, dy)] 96 | dt = 0.01 97 | t = 0 98 | for i in range(101): 99 | dx, dy = tocubic.bz_eval(dbz, t) 100 | ddx, ddy = tocubic.bz_eval(ddbz, t) 101 | k = (ddy * dx - dy * ddx) / (dx * dx + dy * dy) ** 1.5 102 | print 100 + 500 * ss[0], 100 + 200 * k, cmd 103 | cmd = 'lineto' 104 | 105 | dsdx = arclength_deriv(t, ss) 106 | tocubic.rk4(ss, dsdx, t, .01, arclength_deriv) 107 | t += dt 108 | print 'stroke' 109 | 110 | def plot_k_nominal(s0, s1): 111 | k0 = 2 * s0 112 | k1 = 2 * s1 113 | print 'gsave 0.5 setlinewidth' 114 | print 100, 100 + 200 * k0, 'moveto' 115 | print 100 + 500 * (s1 - s0), 100 + 200 * k1, 'lineto' 116 | print 'stroke grestore' 117 | 118 | def simple_bez(): 119 | eps_prologue(95, 126, 552, 508, 0) 120 | tocubic.plot_prolog() 121 | print '/ss 1.5 def' 122 | print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' 123 | bz, score = cornu_to_cubic(.5, 1.1, 2) 124 | fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True) 125 | print 'stroke' 126 | print '/Times-Roman 12 selectfont' 127 | print '95 130 moveto ((x0, y0)) show' 128 | print '360 200 moveto ((x1, y1)) show' 129 | print '480 340 moveto ((x2, y2)) show' 130 | print '505 495 moveto ((x3, y3)) show' 131 | print 'showpage' 132 | eps_trailer() 133 | 134 | def fast_bez(figno): 135 | if figno == 3: 136 | y1 = 520 137 | else: 138 | y1 = 550 139 | eps_prologue(95, 140, 552, y1, 0) 140 | tocubic.plot_prolog() 141 | print '/ss 1.5 def' 142 | print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' 143 | bz, score = cornu_to_cubic(.5, 1.1, figno) 144 | fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True) 145 | print 'stroke' 146 | plot_k_nominal(.5, 1.1) 147 | plot_k_of_bz(bz) 148 | print 'showpage' 149 | eps_trailer() 150 | 151 | def bezfig(s1): 152 | eps_prologue(95, 38, 510, 550, 0) 153 | #print '0.5 0.5 scale 500 100 translate' 154 | tocubic.plot_prolog() 155 | print '/ss 1.5 def' 156 | print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' 157 | bz, score = cornu_to_cubic(.5, 0.85, 1) 158 | fromcubic.plot_bzs([[bz]], (-400, 0), 1000, True) 159 | print 'stroke' 160 | plot_k_nominal(.5, 0.85) 161 | plot_k_of_bz(bz) 162 | bz, score = cornu_to_cubic(.5, 0.85, 2) 163 | fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True) 164 | print 'stroke' 165 | print 'gsave 0 50 translate' 166 | plot_k_nominal(.5, .85) 167 | plot_k_of_bz(bz) 168 | print 'grestore' 169 | print 'showpage' 170 | 171 | import sys 172 | 173 | if __name__ == '__main__': 174 | figno = int(sys.argv[1]) 175 | if figno == 0: 176 | simple_bez() 177 | elif figno == 1: 178 | bezfig(1.0) 179 | elif figno == 2: 180 | bezfig(0.85) 181 | else: 182 | fast_bez(figno) 183 | #fast_bez(4) 184 | -------------------------------------------------------------------------------- /curves/cloth_off.py: -------------------------------------------------------------------------------- 1 | # Fancy new algorithms for computing the offset of a clothoid. 2 | 3 | -------------------------------------------------------------------------------- /curves/clothoid.py: -------------------------------------------------------------------------------- 1 | from math import * 2 | import cornu 3 | 4 | def mod_2pi(th): 5 | u = th / (2 * pi) 6 | return 2 * pi * (u - floor(u + 0.5)) 7 | 8 | # Given clothoid k(s) = k0 + k1 s, compute th1 - th0 of chord from s = -.5 9 | # to .5. 10 | def compute_dth(k0, k1): 11 | if k1 < 0: 12 | return -compute_dth(k0, -k1) 13 | elif k1 == 0: 14 | return 0 15 | sqrk1 = sqrt(2 * k1) 16 | t0 = (k0 - .5 * k1) / sqrk1 17 | t1 = (k0 + .5 * k1) / sqrk1 18 | (y0, x0) = cornu.eval_cornu(t0) 19 | (y1, x1) = cornu.eval_cornu(t1) 20 | chord_th = atan2(y1 - y0, x1 - x0) 21 | return mod_2pi(t1 * t1 - chord_th) - mod_2pi(chord_th - t0 * t0) 22 | 23 | def compute_chord(k0, k1): 24 | if k1 == 0: 25 | if k0 == 0: 26 | return 1 27 | else: 28 | return sin(k0 * .5) / (k0 * .5) 29 | sqrk1 = sqrt(2 * abs(k1)) 30 | t0 = (k0 - .5 * k1) / sqrk1 31 | t1 = (k0 + .5 * k1) / sqrk1 32 | (y0, x0) = cornu.eval_cornu(t0) 33 | (y1, x1) = cornu.eval_cornu(t1) 34 | return hypot(y1 - y0, x1 - x0) / abs(t1 - t0) 35 | 36 | # Given th0 and th1 at endpoints (measured from chord), return k0 37 | # and k1 such that the clothoid k(s) = k0 + k1 s, evaluated from 38 | # s = -.5 to .5, has the tangents given 39 | def solve_clothoid(th0, th1, verbose = False): 40 | k0 = th0 + th1 41 | 42 | # initial guess 43 | k1 = 6 * (th1 - th0) 44 | error = (th1 - th0) - compute_dth(k0, k1) 45 | if verbose: 46 | print k0, k1, error 47 | 48 | k1_old, error_old = k1, error 49 | # second guess based on d(dth)/dk1 ~ 1/6 50 | k1 += 6 * error 51 | error = (th1 - th0) - compute_dth(k0, k1) 52 | if verbose: 53 | print k0, k1, error 54 | 55 | # secant method 56 | for i in range(10): 57 | if abs(error) < 1e-9: break 58 | k1_old, error_old, k1 = k1, error, k1 + (k1_old - k1) * error / (error - error_old) 59 | error = (th1 - th0) - compute_dth(k0, k1) 60 | if verbose: 61 | print k0, k1, error 62 | 63 | return k0, k1 64 | 65 | if __name__ == '__main__': 66 | print solve_clothoid(.06, .05, True) 67 | -------------------------------------------------------------------------------- /curves/cornu.py: -------------------------------------------------------------------------------- 1 | from math import * 2 | 3 | # implementation adapted from cephes 4 | 5 | def polevl(x, coef): 6 | ans = coef[-1] 7 | for i in range(len(coef) - 2, -1, -1): 8 | ans = ans * x + coef[i] 9 | return ans 10 | 11 | sn = [ 12 | -2.99181919401019853726E3, 13 | 7.08840045257738576863E5, 14 | -6.29741486205862506537E7, 15 | 2.54890880573376359104E9, 16 | -4.42979518059697779103E10, 17 | 3.18016297876567817986E11 18 | ] 19 | sn.reverse() 20 | sd = [ 21 | 1.00000000000000000000E0, 22 | 2.81376268889994315696E2, 23 | 4.55847810806532581675E4, 24 | 5.17343888770096400730E6, 25 | 4.19320245898111231129E8, 26 | 2.24411795645340920940E10, 27 | 6.07366389490084639049E11 28 | ] 29 | sd.reverse() 30 | cn = [ 31 | -4.98843114573573548651E-8, 32 | 9.50428062829859605134E-6, 33 | -6.45191435683965050962E-4, 34 | 1.88843319396703850064E-2, 35 | -2.05525900955013891793E-1, 36 | 9.99999999999999998822E-1 37 | ] 38 | cn.reverse() 39 | cd = [ 40 | 3.99982968972495980367E-12, 41 | 9.15439215774657478799E-10, 42 | 1.25001862479598821474E-7, 43 | 1.22262789024179030997E-5, 44 | 8.68029542941784300606E-4, 45 | 4.12142090722199792936E-2, 46 | 1.00000000000000000118E0 47 | ] 48 | cd.reverse() 49 | 50 | fn = [ 51 | 4.21543555043677546506E-1, 52 | 1.43407919780758885261E-1, 53 | 1.15220955073585758835E-2, 54 | 3.45017939782574027900E-4, 55 | 4.63613749287867322088E-6, 56 | 3.05568983790257605827E-8, 57 | 1.02304514164907233465E-10, 58 | 1.72010743268161828879E-13, 59 | 1.34283276233062758925E-16, 60 | 3.76329711269987889006E-20 61 | ] 62 | fn.reverse() 63 | fd = [ 64 | 1.00000000000000000000E0, 65 | 7.51586398353378947175E-1, 66 | 1.16888925859191382142E-1, 67 | 6.44051526508858611005E-3, 68 | 1.55934409164153020873E-4, 69 | 1.84627567348930545870E-6, 70 | 1.12699224763999035261E-8, 71 | 3.60140029589371370404E-11, 72 | 5.88754533621578410010E-14, 73 | 4.52001434074129701496E-17, 74 | 1.25443237090011264384E-20 75 | ] 76 | fd.reverse() 77 | gn = [ 78 | 5.04442073643383265887E-1, 79 | 1.97102833525523411709E-1, 80 | 1.87648584092575249293E-2, 81 | 6.84079380915393090172E-4, 82 | 1.15138826111884280931E-5, 83 | 9.82852443688422223854E-8, 84 | 4.45344415861750144738E-10, 85 | 1.08268041139020870318E-12, 86 | 1.37555460633261799868E-15, 87 | 8.36354435630677421531E-19, 88 | 1.86958710162783235106E-22 89 | ] 90 | gn.reverse() 91 | gd = [ 92 | 1.00000000000000000000E0, 93 | 1.47495759925128324529E0, 94 | 3.37748989120019970451E-1, 95 | 2.53603741420338795122E-2, 96 | 8.14679107184306179049E-4, 97 | 1.27545075667729118702E-5, 98 | 1.04314589657571990585E-7, 99 | 4.60680728146520428211E-10, 100 | 1.10273215066240270757E-12, 101 | 1.38796531259578871258E-15, 102 | 8.39158816283118707363E-19, 103 | 1.86958710162783236342E-22 104 | ] 105 | gd.reverse() 106 | 107 | 108 | def fresnel(xxa): 109 | x = abs(xxa) 110 | x2 = x * x 111 | if x2 < 2.5625: 112 | t = x2 * x2 113 | ss = x * x2 * polevl(t, sn) / polevl(t, sd) 114 | cc = x * polevl(t, cn) / polevl(t, cd) 115 | elif x > 36974.0: 116 | ss = 0.5 117 | cc = 0.5 118 | else: 119 | t = pi * x2 120 | u = 1.0 / (t * t) 121 | t = 1.0 / t 122 | f = 1.0 - u * polevl(u, fn) / polevl(u, fd) 123 | g = t * polevl(u, gn) / polevl(u, gd) 124 | t = pi * .5 * x2 125 | c = cos(t) 126 | s = sin(t) 127 | t = pi * x 128 | cc = 0.5 + (f * s - g * c) / t 129 | ss = 0.5 - (f * c + g * s) / t 130 | if xxa < 0: 131 | cc = -cc 132 | ss = -ss 133 | return ss, cc 134 | 135 | def eval_cornu(t): 136 | spio2 = sqrt(pi * .5) 137 | s, c = fresnel(t / spio2) 138 | s *= spio2 139 | c *= spio2 140 | return s, c 141 | -------------------------------------------------------------------------------- /curves/euler-elastica.py: -------------------------------------------------------------------------------- 1 | from math import * 2 | 3 | def plot_elastica(a, c): 4 | s = 500 5 | cmd = 'moveto' 6 | dx = .001 7 | x, y = 0, 0 8 | if c * c > 2 * a * a: 9 | g = sqrt(c * c - 2 * a * a) 10 | x = g + .01 11 | if c == 0: 12 | x = .001 13 | try: 14 | for i in range(1000): 15 | print 6 + s * x, 200 + s * y, cmd 16 | cmd = 'lineto' 17 | x += dx 18 | if 1 and c * c > 2 * a * a: 19 | print (c * c - x * x) * (x * x - g * g) 20 | dy = dx * (x * x - .5 * c * c - .5 * g * g) / sqrt((c * c - x * x) * (x * x - g * g)) 21 | else: 22 | dy = dx * (a * a - c * c + x * x)/sqrt((c * c - x * x) * (2 * a * a - c * c + x * x)) 23 | y += dy 24 | except ValueError, e: 25 | pass 26 | print 'stroke' 27 | 28 | plot_elastica(1, 0) 29 | print 'showpage' 30 | -------------------------------------------------------------------------------- /curves/fromcubic.py: -------------------------------------------------------------------------------- 1 | # Convert piecewise cubic into piecewise clothoid representation. 2 | 3 | from math import * 4 | 5 | import clothoid 6 | import pcorn 7 | import tocubic 8 | 9 | import offset 10 | 11 | def read_bz(f): 12 | result = [] 13 | for l in f.xreadlines(): 14 | s = l.split() 15 | if len(s) > 0: 16 | cmd = s[-1] 17 | #print s[:-1], cmd 18 | if cmd == 'm': 19 | sp = [] 20 | result.append(sp) 21 | curpt = [float(x) for x in s[0:2]] 22 | startpt = curpt 23 | elif cmd == 'l': 24 | newpt = [float(x) for x in s[0:2]] 25 | sp.append((curpt, newpt)) 26 | curpt = newpt 27 | elif cmd == 'c': 28 | c1 = [float(x) for x in s[0:2]] 29 | c2 = [float(x) for x in s[2:4]] 30 | newpt = [float(x) for x in s[4:6]] 31 | sp.append((curpt, c1, c2, newpt)) 32 | curpt = newpt 33 | return result 34 | 35 | def plot_bzs(bzs, z0, scale, fancy = False): 36 | for sp in bzs: 37 | for i in range(len(sp)): 38 | bz = sp[i] 39 | tocubic.plot_bz(bz, z0, scale, i == 0) 40 | print 'stroke' 41 | if fancy: 42 | for i in range(len(sp)): 43 | bz = sp[i] 44 | 45 | x0, y0 = z0[0] + scale * bz[0][0], z0[1] + scale * bz[0][1] 46 | print 'gsave', x0, y0, 'translate circle fill grestore' 47 | if len(bz) == 4: 48 | x1, y1 = z0[0] + scale * bz[1][0], z0[1] + scale * bz[1][1] 49 | x2, y2 = z0[0] + scale * bz[2][0], z0[1] + scale * bz[2][1] 50 | x3, y3 = z0[0] + scale * bz[3][0], z0[1] + scale * bz[3][1] 51 | print 'gsave 0.5 setlinewidth', x0, y0, 'moveto' 52 | print x1, y1, 'lineto stroke' 53 | print x2, y2, 'moveto' 54 | print x3, y3, 'lineto stroke grestore' 55 | print 'gsave', x1, y1, 'translate 0.75 dup scale circle fill grestore' 56 | print 'gsave', x2, y2, 'translate 0.75 dup scale circle fill grestore' 57 | print 'gsave', x3, y3, 'translate 0.75 dup scale circle fill grestore' 58 | 59 | 60 | 61 | def measure_bz_cloth(seg, bz, n = 100): 62 | bz_arclen = tocubic.bz_arclength_rk4(bz) 63 | arclen_ratio = seg.arclen / bz_arclen 64 | dbz = tocubic.bz_deriv(bz) 65 | 66 | def measure_derivs(x, ys): 67 | dx, dy = tocubic.bz_eval(dbz, x) 68 | ds = hypot(dx, dy) 69 | s = ys[0] * arclen_ratio 70 | dscore = ds * (tocubic.mod_2pi(atan2(dy, dx) - seg.th(s)) ** 2) 71 | #print s, atan2(dy, dx), seg.th(s) 72 | return [ds, dscore] 73 | dt = 1./n 74 | t = 0 75 | ys = [0, 0] 76 | for i in range(n): 77 | dydx = measure_derivs(t, ys) 78 | tocubic.rk4(ys, dydx, t, dt, measure_derivs) 79 | t += dt 80 | return ys[1] 81 | 82 | def cubic_bz_to_pcorn(bz, thresh): 83 | dx = bz[3][0] - bz[0][0] 84 | dy = bz[3][1] - bz[0][1] 85 | dx1 = bz[1][0] - bz[0][0] 86 | dy1 = bz[1][1] - bz[0][1] 87 | dx2 = bz[3][0] - bz[2][0] 88 | dy2 = bz[3][1] - bz[2][1] 89 | chth = atan2(dy, dx) 90 | th0 = tocubic.mod_2pi(chth - atan2(dy1, dx1)) 91 | th1 = tocubic.mod_2pi(atan2(dy2, dx2) - chth) 92 | seg = pcorn.Segment(bz[0], bz[3], th0, th1) 93 | err = measure_bz_cloth(seg, bz) 94 | if err < thresh: 95 | return [seg] 96 | else: 97 | # de Casteljau 98 | x01, y01 = 0.5 * (bz[0][0] + bz[1][0]), 0.5 * (bz[0][1] + bz[1][1]) 99 | x12, y12 = 0.5 * (bz[1][0] + bz[2][0]), 0.5 * (bz[1][1] + bz[2][1]) 100 | x23, y23 = 0.5 * (bz[2][0] + bz[3][0]), 0.5 * (bz[2][1] + bz[3][1]) 101 | xl2, yl2 = 0.5 * (x01 + x12), 0.5 * (y01 + y12) 102 | xr1, yr1 = 0.5 * (x12 + x23), 0.5 * (y12 + y23) 103 | xm, ym = 0.5 * (xl2 + xr1), 0.5 * (yl2 + yr1) 104 | bzl = [bz[0], (x01, y01), (xl2, yl2), (xm, ym)] 105 | bzr = [(xm, ym), (xr1, yr1), (x23, y23), bz[3]] 106 | segs = cubic_bz_to_pcorn(bzl, 0.5 * thresh) 107 | segs.extend(cubic_bz_to_pcorn(bzr, 0.5 * thresh)) 108 | return segs 109 | 110 | def bzs_to_pcorn(bzs, thresh = 1e-9): 111 | result = [] 112 | for sp in bzs: 113 | rsp = [] 114 | for bz in sp: 115 | if len(bz) == 2: 116 | dx = bz[1][0] - bz[0][0] 117 | dy = bz[1][1] - bz[0][1] 118 | th = atan2(dy, dx) 119 | rsp.append(pcorn.Segment(bz[0], bz[1], 0, 0)) 120 | else: 121 | rsp.extend(cubic_bz_to_pcorn(bz, thresh)) 122 | result.append(rsp) 123 | return result 124 | 125 | def plot_segs(segs): 126 | for i in range(len(segs)): 127 | seg = segs[i] 128 | if i == 0: 129 | print seg.z0[0], seg.z0[1], 'moveto' 130 | print seg.z1[0], seg.z1[1], 'lineto' 131 | print 'stroke' 132 | for i in range(len(segs)): 133 | seg = segs[i] 134 | if i == 0: 135 | print 'gsave', seg.z0[0], seg.z0[1], 'translate circle fill grestore' 136 | print 'gsave', seg.z1[0], seg.z1[1], 'translate circle fill grestore' 137 | 138 | import sys 139 | 140 | def test_to_pcorn(): 141 | C1 = 0.55228 142 | bz = [(100, 100), (100 + 400 * C1, 100), (500, 500 - 400 * C1), (500, 500)] 143 | for i in range(0, 13): 144 | thresh = .1 ** i 145 | segs = cubic_bz_to_pcorn(bz, thresh) 146 | plot_segs(segs) 147 | print >> sys.stderr, thresh, len(segs) 148 | print '0 20 translate' 149 | 150 | if __name__ == '__main__': 151 | f = file(sys.argv[1]) 152 | bzs = read_bz(f) 153 | rsps = bzs_to_pcorn(bzs, 1) 154 | #print rsps 155 | tocubic.plot_prolog() 156 | print 'grestore' 157 | print '1 -1 scale 0 -720 translate' 158 | print '/ss 1.5 def' 159 | print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' 160 | tot = 0 161 | for segs in rsps: 162 | curve = pcorn.Curve(segs) 163 | #curve = offset.offset(curve, 10) 164 | print '%', curve.arclen 165 | print '%', curve.sstarts 166 | if 0: 167 | print 'gsave 1 0 0 setrgbcolor' 168 | cmd = 'moveto' 169 | for i in range(100): 170 | s = i * .01 * curve.arclen 171 | x, y = curve.xy(s) 172 | th = curve.th(s) 173 | sth = 5 * sin(th) 174 | cth = 5 * cos(th) 175 | print x, y, cmd 176 | cmd = 'lineto' 177 | print 'closepath stroke grestore' 178 | for i in range(100): 179 | s = i * .01 * curve.arclen 180 | x, y = curve.xy(s) 181 | th = curve.th(s) 182 | sth = 5 * sin(th) 183 | cth = 5 * cos(th) 184 | if 0: 185 | print x - cth, y - sth, 'moveto' 186 | print x + cth, y + sth, 'lineto stroke' 187 | if 1: 188 | for s in curve.find_breaks(): 189 | print 'gsave 0 1 0 setrgbcolor' 190 | x, y = curve.xy(s) 191 | print x, y, 'translate 2 dup scale circle fill' 192 | print 'grestore' 193 | #plot_segs(segs) 194 | 195 | print 'gsave 0 0 0 setrgbcolor' 196 | optim = 3 197 | thresh = 1e-2 198 | new_bzs = tocubic.pcorn_curve_to_bzs(curve, optim, thresh) 199 | tot += len(new_bzs) 200 | plot_bzs([new_bzs], (0, 0), 1, True) 201 | print 'grestore' 202 | print 'grestore' 203 | print '/Helvetica 12 selectfont' 204 | print '36 720 moveto (thresh=%g optim=%d) show' % (thresh, optim) 205 | print '( tot segs=%d) show' % tot 206 | print 'showpage' 207 | 208 | #plot_bzs(bzs, (100, 100), 1) 209 | -------------------------------------------------------------------------------- /curves/mvc.py: -------------------------------------------------------------------------------- 1 | from math import * 2 | import array 3 | import sys 4 | import random 5 | 6 | def run_mvc(k, k1, k2, k3, C, n = 100, do_print = False): 7 | cmd = 'moveto' 8 | result = array.array('d') 9 | cost = 0 10 | th = 0 11 | x = 0 12 | y = 0 13 | dt = 1.0 / n 14 | for i in range(n): 15 | k4 = -k * (k * k2 - .5 * k1 * k1 + C) 16 | 17 | cost += dt * k1 * k1 18 | x += dt * cos(th) 19 | y += dt * sin(th) 20 | th += dt * k 21 | 22 | k += dt * k1 23 | k1 += dt * k2 24 | k2 += dt * k3 25 | k3 += dt * k4 26 | result.append(k) 27 | if do_print: print 400 + 400 * x, 500 + 400 * y, cmd 28 | cmd = 'lineto' 29 | return result, cost, x, y, th 30 | 31 | def run_mec_cos(k, lam1, lam2, n = 100, do_print = False): 32 | cmd = 'moveto' 33 | result = array.array('d') 34 | cost = 0 35 | th = 0 36 | x = 0 37 | y = 0 38 | dt = 1.0 / n 39 | for i in range(n): 40 | k1 = lam1 * cos(th) + lam2 * sin(th) 41 | 42 | cost += dt * k * k 43 | x += dt * cos(th) 44 | y += dt * sin(th) 45 | th += dt * k 46 | 47 | k += dt * k1 48 | result.append(k) 49 | if do_print: print 400 + 400 * x, 500 + 400 * y, cmd 50 | cmd = 'lineto' 51 | return result, cost, x, y, th 52 | 53 | def descend(params, fnl): 54 | delta = 1 55 | for i in range(100): 56 | best = fnl(params, i, True) 57 | bestparams = params 58 | for j in range(2 * len(params)): 59 | ix = j / 2 60 | sign = 1 - 2 * (ix & 1) 61 | newparams = params[:] 62 | newparams[ix] += delta * sign 63 | new = fnl(newparams, i) 64 | if (new < best): 65 | bestparams = newparams 66 | best = new 67 | if (bestparams == params): 68 | delta *= .5 69 | print '%', params, delta 70 | sys.stdout.flush() 71 | params = bestparams 72 | return bestparams 73 | 74 | def descend2(params, fnl): 75 | delta = 20 76 | for i in range(5): 77 | best = fnl(params, i, True) 78 | bestparams = params 79 | for j in range(100000): 80 | newparams = params[:] 81 | for ix in range(len(params)): 82 | newparams[ix] += delta * (2 * random.random() - 1) 83 | new = fnl(newparams, i) 84 | if (new < best): 85 | bestparams = newparams 86 | best = new 87 | if (bestparams == params): 88 | delta *= .5 89 | params = bestparams 90 | print '%', params, best, delta 91 | sys.stdout.flush() 92 | return bestparams 93 | 94 | def desc_eval(params, dfdp, fnl, i, x): 95 | newparams = params[:] 96 | for ix in range(len(params)): 97 | newparams[ix] += x * dfdp[ix] 98 | return fnl(newparams, i) 99 | 100 | def descend3(params, fnl): 101 | dp = 1e-6 102 | for i in range(1000): 103 | base = fnl(params, i, True) 104 | dfdp = [] 105 | for ix in range(len(params)): 106 | newparams = params[:] 107 | newparams[ix] += dp 108 | new = fnl(newparams, i) 109 | dfdp.append((new - base) / dp) 110 | print '% dfdp = ', dfdp 111 | xr = 0. 112 | yr = base 113 | xm = -1e-3 114 | ym = desc_eval(params, dfdp, fnl, i, xm) 115 | if ym > yr: 116 | while ym > yr: 117 | xl, yl = xm, ym 118 | xm = .618034 * xl 119 | ym = desc_eval(params, dfdp, fnl, i, xm) 120 | else: 121 | xl = 1.618034 * xm 122 | yl = desc_eval(params, dfdp, fnl, i, xl) 123 | while ym > yl: 124 | xm, ym = xl, yl 125 | xl = 1.618034 * xm 126 | yl = desc_eval(params, dfdp, fnl, i, xl) 127 | 128 | # We have initial bracket; ym < yl and ym < yr 129 | 130 | x0, x3 = xl, xr 131 | if abs(xr - xm) > abs(xm - xl): 132 | x1, y1 = xm, ym 133 | x2 = xm + .381966 * (xr - xm) 134 | y2 = desc_eval(params, dfdp, fnl, i, x2) 135 | else: 136 | x2, y2 = xm, ym 137 | x1 = xm + .381966 * (xl - xm) 138 | y1 = desc_eval(params, dfdp, fnl, i, x1) 139 | for j in range(30): 140 | if y2 < y1: 141 | x0, x1, x2 = x1, x2, x2 + .381966 * (x3 - x2) 142 | y0, y1 = y1, y2 143 | y2 = desc_eval(params, dfdp, fnl, i, x2) 144 | else: 145 | x1, x2, x3 = x1 + .381966 * (x0 - x1), x1, x2 146 | y1 = desc_eval(params, dfdp, fnl, i, x1) 147 | if y1 < y2: 148 | xbest = x1 149 | ybest = y1 150 | else: 151 | xbest = x2 152 | ybest = y2 153 | for ix in range(len(params)): 154 | params[ix] += xbest * dfdp[ix] 155 | print '%', params, xbest, ybest 156 | sys.stdout.flush() 157 | return params 158 | 159 | def mk_mvc_fnl(th0, th1): 160 | def fnl(params, i, do_print = False): 161 | k, k1, k2, k3, C = params 162 | ks, cost, x, y, th = run_mvc(k, k1, k2, k3, C, 100) 163 | cost *= hypot(y, x) ** 3 164 | actual_th0 = atan2(y, x) 165 | actual_th1 = th - actual_th0 166 | if do_print: print '%', x, y, actual_th0, actual_th1, cost 167 | err = (th0 - actual_th0) ** 2 + (th1 - actual_th1) ** 2 168 | multiplier = 1000 169 | return cost + err * multiplier 170 | return fnl 171 | 172 | def mk_mec_fnl(th0, th1): 173 | def fnl(params, i, do_print = False): 174 | k, lam1, lam2 = params 175 | ks, cost, x, y, th = run_mec_cos(k, lam1, lam2) 176 | cost *= hypot(y, x) 177 | actual_th0 = atan2(y, x) 178 | actual_th1 = th - actual_th0 179 | if do_print: print '%', x, y, actual_th0, actual_th1, cost 180 | err = (th0 - actual_th0) ** 2 + (th1 - actual_th1) ** 2 181 | multiplier = 10 182 | return cost + err * multiplier 183 | return fnl 184 | 185 | #ks, cost, x, y, th = run_mvc(0, 10, -10, 10, 200) 186 | #print '%', cost, x, y 187 | #print 'stroke showpage' 188 | 189 | def mvc_test(): 190 | fnl = mk_mvc_fnl(-pi, pi/4) 191 | params = [0, 0, 0, 0, 0] 192 | params = descend3(params, fnl) 193 | k, k1, k2, k3, C = params 194 | run_mvc(k, k1, k2, k3, C, 100, True) 195 | print 'stroke showpage' 196 | print '%', params 197 | 198 | def mec_test(): 199 | th0, th1 = pi/2, pi/2 200 | fnl = mk_mec_fnl(th0, th1) 201 | params = [0, 0, 0] 202 | params = descend2(params, fnl) 203 | k, lam1, lam2 = params 204 | run_mec_cos(k, lam1, lam2, 1000, True) 205 | print 'stroke showpage' 206 | print '%', params 207 | 208 | mvc_test() 209 | -------------------------------------------------------------------------------- /curves/numintsynth.py: -------------------------------------------------------------------------------- 1 | # Synthesize a procedure to numerically integrate the 3rd order poly spiral 2 | 3 | tex = False 4 | 5 | if tex: 6 | mulsym = ' ' 7 | else: 8 | mulsym = ' * ' 9 | 10 | class Poly: 11 | def __init__(self, p0, coeffs): 12 | self.p0 = p0 13 | self.coeffs = coeffs 14 | def eval(self, x): 15 | y = x ** self.p0 16 | z = 0 17 | for c in coeffs: 18 | z += y * c 19 | y *= x 20 | return z 21 | 22 | def add(poly0, poly1, nmax): 23 | lp0 = len(poly0.coeffs) 24 | lp1 = len(poly1.coeffs) 25 | p0 = min(poly0.p0, poly1.p0) 26 | n = min(max(poly0.p0 + lp0, poly1.p1 + lp1), nmax) - p0 27 | if n <= 0: return Poly(0, []) 28 | coeffs = [] 29 | for i in range(n): 30 | c = 0 31 | if i >= poly0.p0 - p0 and i < lp0 + poly0.p0 - p0: 32 | c += poly0.coeffs[i + p0 - poly0.p0] 33 | if i >= poly1.p0 - p0 and i < lp1 + poly1.p0 - p0: 34 | c += poly1.coeffs[i + p0 - poly1.p0] 35 | coeffs.append(c) 36 | return Poly(p0, coeffs) 37 | 38 | def pr(str): 39 | if tex: 40 | print str, '\\\\' 41 | else: 42 | print '\t' + str + ';' 43 | 44 | def prd(str): 45 | if tex: 46 | print str, '\\\\' 47 | else: 48 | print '\tdouble ' + str + ';' 49 | 50 | def polymul(p0, p1, degree, basename, suppress_odd = False): 51 | result = [] 52 | for i in range(min(degree, len(p0) + len(p1) - 1)): 53 | terms = [] 54 | for j in range(i + 1): 55 | if j < len(p0) and i - j < len(p1): 56 | t0 = p0[j] 57 | t1 = p1[i - j] 58 | if t0 != None and t1 != None: 59 | terms.append(t0 + mulsym + t1) 60 | if terms == []: 61 | result.append(None) 62 | else: 63 | var = basename % i 64 | if (j % 2 == 0) or not suppress_odd: 65 | prd(var + ' = ' + ' + '.join(terms)) 66 | result.append(var) 67 | return result 68 | 69 | def polysquare(p0, degree, basename): 70 | result = [] 71 | for i in range(min(degree, 2 * len(p0) - 1)): 72 | terms = [] 73 | for j in range((i + 1)/ 2): 74 | if i - j < len(p0): 75 | t0 = p0[j] 76 | t1 = p0[i - j] 77 | if t0 != None and t1 != None: 78 | terms.append(t0 + mulsym + t1) 79 | if len(terms) >= 1: 80 | if tex and len(terms) == 1: 81 | terms = ['2 ' + terms[0]] 82 | else: 83 | terms = ['2' + mulsym + '(' + ' + '.join(terms) + ')'] 84 | if (i % 2) == 0: 85 | t = p0[i / 2] 86 | if t != None: 87 | if tex: 88 | terms.append(t + '^2') 89 | else: 90 | terms.append(t + mulsym + t) 91 | if terms == []: 92 | result.append(None) 93 | else: 94 | var = basename % i 95 | prd(var + ' = ' + ' + '.join(terms)) 96 | result.append(var) 97 | return result 98 | 99 | def mkspiro(degree): 100 | if tex: 101 | us = ['u = 1'] 102 | vs = ['v ='] 103 | else: 104 | us = ['u = 1'] 105 | vs = ['v = 0'] 106 | if tex: 107 | tp = [None, 't_{11}', 't_{12}', 't_{13}', 't_{14}'] 108 | else: 109 | tp = [None, 't1_1', 't1_2', 't1_3', 't1_4'] 110 | if tex: 111 | prd(tp[1] + ' = k_0') 112 | prd(tp[2] + ' = \\frac{k_1}{2}') 113 | prd(tp[3] + ' = \\frac{k_2}{6}') 114 | prd(tp[4] + ' = \\frac{k_3}{24}') 115 | else: 116 | prd(tp[1] + ' = km0') 117 | prd(tp[2] + ' = .5 * km1') 118 | prd(tp[3] + ' = (1./6) * km2') 119 | prd(tp[4] + ' = (1./24) * km3') 120 | tlast = tp 121 | coef = 1. 122 | for i in range(1, degree - 1): 123 | tmp = [] 124 | tcoef = coef 125 | #print tlast 126 | for j in range(len(tlast)): 127 | c = tcoef / (j + 1) 128 | if (j % 2) == 0 and tlast[j] != None: 129 | if tex: 130 | tmp.append('\\frac{%s}{%.0f}' % (tlast[j], 1./c)) 131 | else: 132 | if c < 1e-9: 133 | cstr = '%.16e' % c 134 | else: 135 | cstr = '(1./%d)' % int(.5 + (1./c)) 136 | tmp.append(cstr + ' * ' + tlast[j]) 137 | tcoef *= .5 138 | if tmp != []: 139 | sign = ('+', '-')[(i / 2) % 2] 140 | var = ('u', 'v')[i % 2] 141 | if tex: 142 | if i == 1: pref = '' 143 | else: pref = sign + ' ' 144 | str = pref + (' ' + sign + ' ').join(tmp) 145 | else: 146 | str = var + ' ' + sign + '= ' + ' + '.join(tmp) 147 | if var == 'u': us.append(str) 148 | else: vs.append(str) 149 | if i < degree - 1: 150 | if tex: 151 | basename = 't_{%d%%d}' % (i + 1) 152 | else: 153 | basename = 't%d_%%d' % (i + 1) 154 | if i == 1: 155 | tnext = polysquare(tp, degree - 1, basename) 156 | t2 = tnext 157 | elif i == 3: 158 | tnext = polysquare(t2l, degree - 1, basename) 159 | elif (i % 2) == 0: 160 | tnext = polymul(tlast, tp, degree - 1, basename, True) 161 | else: 162 | tnext = polymul(t2l, t2, degree - 1, basename) 163 | t2l = tlast 164 | tlast = tnext 165 | coef /= (i + 1) 166 | if tex: 167 | pr(' '.join(us)) 168 | pr(' '.join(vs)) 169 | else: 170 | for u in us: 171 | pr(u) 172 | for v in vs: 173 | pr(v) 174 | 175 | if __name__ == '__main__': 176 | mkspiro(12) 177 | -------------------------------------------------------------------------------- /curves/offset.py: -------------------------------------------------------------------------------- 1 | # offset curve of piecewise cornu curves 2 | 3 | from math import * 4 | 5 | import pcorn 6 | from clothoid import mod_2pi 7 | 8 | def seg_offset(seg, d): 9 | th0 = seg.th(0) 10 | th1 = seg.th(seg.arclen) 11 | z0 = [seg.z0[0] + d * sin(th0), seg.z0[1] - d * cos(th0)] 12 | z1 = [seg.z1[0] + d * sin(th1), seg.z1[1] - d * cos(th1)] 13 | chth = atan2(z1[1] - z0[1], z1[0] - z0[0]) 14 | return [pcorn.Segment(z0, z1, mod_2pi(chth - th0), mod_2pi(th1 - chth))] 15 | 16 | 17 | def offset(curve, d): 18 | segs = [] 19 | for seg in curve.segs: 20 | segs.extend(seg_offset(seg, d)) 21 | return pcorn.Curve(segs) 22 | -------------------------------------------------------------------------------- /curves/pcorn.py: -------------------------------------------------------------------------------- 1 | # Utilities for piecewise cornu representation of curves 2 | 3 | from math import * 4 | 5 | import clothoid 6 | import cornu 7 | 8 | class Segment: 9 | def __init__(self, z0, z1, th0, th1): 10 | self.z0 = z0 11 | self.z1 = z1 12 | self.th0 = th0 13 | self.th1 = th1 14 | self.compute() 15 | def __repr__(self): 16 | return '[' + `self.z0` + `self.z1` + ' ' + `self.th0` + ' ' + `self.th1` + ']' 17 | def compute(self): 18 | dx = self.z1[0] - self.z0[0] 19 | dy = self.z1[1] - self.z0[1] 20 | chord = hypot(dy, dx) 21 | chth = atan2(dy, dx) 22 | k0, k1 = clothoid.solve_clothoid(self.th0, self.th1) 23 | charc = clothoid.compute_chord(k0, k1) 24 | 25 | self.chord = chord 26 | self.chth = chth 27 | self.k0, self.k1 = k0, k1 28 | self.charc = charc 29 | self.arclen = chord / charc 30 | self.thmid = self.chth - self.th0 + 0.5 * self.k0 - 0.125 * self.k1 31 | 32 | self.setup_xy_fresnel() 33 | 34 | def setup_xy_fresnel(self): 35 | k0, k1 = self.k0, self.k1 36 | if k1 == 0: k1 = 1e-6 # hack 37 | if k1 != 0: 38 | sqrk1 = sqrt(2 * abs(k1)) 39 | t0 = (k0 - .5 * k1) / sqrk1 40 | t1 = (k0 + .5 * k1) / sqrk1 41 | (y0, x0) = cornu.eval_cornu(t0) 42 | (y1, x1) = cornu.eval_cornu(t1) 43 | chord_th = atan2(y1 - y0, x1 - x0) 44 | chord = hypot(y1 - y0, x1 - x0) 45 | scale = self.chord / chord 46 | if k1 >= 0: 47 | th = self.chth - chord_th 48 | self.mxx = scale * cos(th) 49 | self.myx = scale * sin(th) 50 | self.mxy = -self.myx 51 | self.myy = self.mxx 52 | else: 53 | th = self.chth + chord_th 54 | self.mxx = scale * cos(th) 55 | self.myx = scale * sin(th) 56 | self.mxy = self.myx 57 | self.myy = -self.mxx 58 | # rotate -chord_th, flip top/bottom, rotate self.chth 59 | self.x0 = self.z0[0] - (self.mxx * x0 + self.mxy * y0) 60 | self.y0 = self.z0[1] - (self.myx * x0 + self.myy * y0) 61 | 62 | def th(self, s): 63 | u = s / self.arclen - 0.5 64 | return self.thmid + (0.5 * self.k1 * u + self.k0) * u 65 | 66 | def xy(self, s): 67 | # using fresnel integrals; polynomial approx might be better 68 | u = s / self.arclen - 0.5 69 | k0, k1 = self.k0, self.k1 70 | if k1 == 0: k1 = 1e-6 # hack 71 | if k1 != 0: 72 | sqrk1 = sqrt(2 * abs(k1)) 73 | t = (k0 + u * k1) / sqrk1 74 | (y, x) = cornu.eval_cornu(t) 75 | return [self.x0 + self.mxx * x + self.mxy * y, 76 | self.y0 + self.myx * x + self.myy * y] 77 | 78 | def find_extrema(self): 79 | # find solutions of th(s) = 0 mod pi/2 80 | # todo: find extra solutions when there's an inflection 81 | th0 = self.thmid + 0.125 * self.k1 - 0.5 * self.k0 82 | th1 = self.thmid + 0.125 * self.k1 + 0.5 * self.k0 83 | twooverpi = 2 / pi 84 | n0 = int(floor(th0 * twooverpi)) 85 | n1 = int(floor(th1 * twooverpi)) 86 | if th1 > th0: signum = 1 87 | else: signum = -1 88 | result = [] 89 | for i in range(n0, n1, signum): 90 | th = pi/2 * (i + 0.5 * (signum + 1)) 91 | a = .5 * self.k1 92 | b = self.k0 93 | c = self.thmid - th 94 | if a == 0: 95 | u1 = -c/b 96 | u2 = 1000 97 | else: 98 | sqrtdiscrim = sqrt(b * b - 4 * a * c) 99 | u1 = (-b - sqrtdiscrim) / (2 * a) 100 | u2 = (-b + sqrtdiscrim) / (2 * a) 101 | if u1 >= -0.5 and u1 < 0.5: 102 | result.append(self.arclen * (u1 + 0.5)) 103 | if u2 >= -0.5 and u2 < 0.5: 104 | result.append(self.arclen * (u2 + 0.5)) 105 | return result 106 | 107 | class Curve: 108 | def __init__(self, segs): 109 | self.segs = segs 110 | self.compute() 111 | def compute(self): 112 | arclen = 0 113 | sstarts = [] 114 | for seg in self.segs: 115 | sstarts.append(arclen) 116 | arclen += seg.arclen 117 | 118 | self.arclen = arclen 119 | self.sstarts = sstarts 120 | def th(self, s, deltas = False): 121 | u = s / self.arclen 122 | s = self.arclen * (u - floor(u)) 123 | if s == 0 and not deltas: s = self.arclen 124 | i = 0 125 | while i < len(self.segs) - 1: 126 | # binary search would make a lot of sense here 127 | snext = self.sstarts[i + 1] 128 | if s < snext or (not deltas and s == snext): 129 | break 130 | i += 1 131 | return self.segs[i].th(s - self.sstarts[i]) 132 | def xy(self, s): 133 | u = s / self.arclen 134 | s = self.arclen * (u - floor(u)) 135 | i = 0 136 | while i < len(self.segs) - 1: 137 | # binary search would make a lot of sense here 138 | if s <= self.sstarts[i + 1]: 139 | break 140 | i += 1 141 | return self.segs[i].xy(s - self.sstarts[i]) 142 | def find_extrema(self): 143 | result = [] 144 | for i in range(len(self.segs)): 145 | seg = self.segs[i] 146 | for s in seg.find_extrema(): 147 | result.append(s + self.sstarts[i]) 148 | return result 149 | def find_breaks(self): 150 | result = [] 151 | for i in range(len(self.segs)): 152 | pseg = self.segs[(i + len(self.segs) - 1) % len(self.segs)] 153 | seg = self.segs[i] 154 | th = clothoid.mod_2pi(pseg.chth + pseg.th1 - (seg.chth - seg.th0)) 155 | print '% pseg', pseg.chth + pseg.th1, 'seg', seg.chth - seg.th0 156 | pisline = pseg.k0 == 0 and pseg.k1 == 0 157 | sisline = seg.k0 == 0 and seg.k1 == 0 158 | if fabs(th) > 1e-3 or (pisline and not sisline) or (sisline and not pisline): 159 | result.append(self.sstarts[i]) 160 | return result 161 | -------------------------------------------------------------------------------- /curves/plot_solve_clothoid.py: -------------------------------------------------------------------------------- 1 | import clothoid 2 | from math import * 3 | 4 | print '%!PS-Adobe' 5 | 6 | def integ_spiro(k0, k1, k2, k3, n = 4): 7 | th1 = k0 8 | th2 = .5 * k1 9 | th3 = (1./6) * k2 10 | th4 = (1./24) * k3 11 | ds = 1. / n 12 | ds2 = ds * ds 13 | ds3 = ds2 * ds 14 | s = .5 * ds - .5 15 | 16 | k0 *= ds 17 | k1 *= ds 18 | k2 *= ds 19 | k3 *= ds 20 | 21 | x = 0 22 | y = 0 23 | 24 | for i in range(n): 25 | if n == 1: 26 | km0 = k0 27 | km1 = k1 * ds 28 | km2 = k2 * ds2 29 | else: 30 | km0 = (((1./6) * k3 * s + .5 * k2) * s + k1) * s + k0 31 | km1 = ((.5 * k3 * s + k2) * s + k1) * ds 32 | km2 = (k3 * s + k2) * ds2 33 | km3 = k3 * ds3 34 | 35 | t1_1 = km0 36 | t1_2 = .5 * km1 37 | t1_3 = (1./6) * km2 38 | t1_4 = (1./24) * km3 39 | t2_2 = t1_1 * t1_1 40 | t2_3 = 2 * (t1_1 * t1_2) 41 | t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2 42 | t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3) 43 | t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3 44 | t2_7 = 2 * (t1_3 * t1_4) 45 | t2_8 = t1_4 * t1_4 46 | t3_4 = t2_2 * t1_2 + t2_3 * t1_1 47 | t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1 48 | t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1 49 | t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2 50 | t4_4 = t2_2 * t2_2 51 | t4_5 = 2 * (t2_2 * t2_3) 52 | t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3 53 | t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4) 54 | t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4 55 | t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5) 56 | t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5 57 | t5_6 = t4_4 * t1_2 + t4_5 * t1_1 58 | t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1 59 | t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1 60 | t6_6 = t4_4 * t2_2 61 | t6_7 = t4_4 * t2_3 + t4_5 * t2_2 62 | t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2 63 | t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2 64 | t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2 65 | t7_8 = t6_6 * t1_2 + t6_7 * t1_1 66 | t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1 67 | t8_8 = t6_6 * t2_2 68 | t8_9 = t6_6 * t2_3 + t6_7 * t2_2 69 | t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2 70 | t9_10 = t8_8 * t1_2 + t8_9 * t1_1 71 | t10_10 = t8_8 * t2_2 72 | u = 1 73 | u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8 74 | u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8 + (1./270336) * t4_10 75 | u -= (1./322560) * t6_6 + (1./1658880) * t6_8 + (1./8110080) * t6_10 76 | u += (1./92897280) * t8_8 + (1./454164480) * t8_10 77 | u -= 2.4464949595157930e-11 * t10_10 78 | v = (1./12) * t1_2 + (1./80) * t1_4 79 | v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8 + (1./67584) * t3_10 80 | v += (1./53760) * t5_6 + (1./276480) * t5_8 + (1./1351680) * t5_10 81 | v -= (1./11612160) * t7_8 + (1./56770560) * t7_10 82 | v += 2.4464949595157932e-10 * t9_10 83 | if n == 1: 84 | x = u 85 | y = v 86 | else: 87 | th = (((th4 * s + th3) * s + th2) * s + th1) * s 88 | cth = cos(th) 89 | sth = sin(th) 90 | 91 | x += cth * u - sth * v 92 | y += cth * v + sth * u 93 | s += ds 94 | return [x * ds, y * ds] 95 | 96 | count_iter = 0 97 | 98 | # Given th0 and th1 at endpoints (measured from chord), return k0 99 | # and k1 such that the clothoid k(s) = k0 + k1 s, evaluated from 100 | # s = -.5 to .5, has the tangents given 101 | def solve_clothoid(th0, th1, verbose = False): 102 | global count_iter 103 | 104 | k1_old = 0 105 | e_old = th1 - th0 106 | k0 = th0 + th1 107 | k1 = 6 * (1 - ((.5 / pi) * k0) ** 3) * e_old 108 | 109 | # secant method 110 | for i in range(10): 111 | x, y = integ_spiro(k0, k1, 0, 0) 112 | e = (th1 - th0) + 2 * atan2(y, x) - .25 * k1 113 | count_iter += 1 114 | if verbose: 115 | print k0, k1, e 116 | if abs(e) < 1e-9: break 117 | k1_old, e_old, k1 = k1, e, k1 + (k1_old - k1) * e / (e - e_old) 118 | 119 | return k0, k1 120 | 121 | def plot_by_thp(): 122 | count = 0 123 | for i in range(11): 124 | thp = i * .1 125 | print .5 + .05 * i, .5, .5, 'setrgbcolor' 126 | print '.75 setlinewidth' 127 | cmd = 'moveto' 128 | for j in range(-40, 41): 129 | thm = j * .02 130 | k0, k1 = solve_clothoid(thp - thm, thp + thm, True) 131 | count += 1 132 | k1 = min(40, max(-40, k1)) 133 | print 306 + 75 * thm, 396 - 10 * k1, cmd 134 | cmd = 'lineto' 135 | print 'stroke' 136 | print '% count_iter = ', count_iter, 'for', count 137 | 138 | def plot_by_thm(): 139 | print '.75 setlinewidth' 140 | print 36, 396 - 350, 'moveto' 141 | print 0, 700, 'rlineto stroke' 142 | for i in range(-10, 10): 143 | if i == 0: wid = 636 144 | else: wid = 5 145 | print 36, 396 - 10 * i, 'moveto', wid, '0 rlineto stroke' 146 | cmd = 'moveto' 147 | thm = -.1 148 | for i in range(41): 149 | thp = i * .1 150 | k0, k1 = solve_clothoid(thp - thm, thp + thm) 151 | print 36 + 150 * thp, 396 - 100 * k1, cmd 152 | cmd = 'lineto' 153 | print 'stroke' 154 | print '0 0 1 setrgbcolor' 155 | cmd = 'moveto' 156 | for i in range(41): 157 | thp = i * .1 158 | k1 = 12 * thm * cos(.5 * thp) 159 | k1 = 12 * thm * (1 - (thp / pi) ** 3) 160 | print 36 + 150 * thp, 396 - 100 * k1, cmd 161 | cmd = 'lineto' 162 | print 'stroke' 163 | 164 | plot_by_thp() 165 | -------------------------------------------------------------------------------- /curves/poly3.py: -------------------------------------------------------------------------------- 1 | # Numerical techniques for solving 3rd order polynomial spline systems 2 | 3 | # The standard representation is the vector of derivatives at s=0, 4 | # with -.5 <= s <= 5. 5 | # 6 | # Thus, \kappa(s) = k0 + k1 s + 1/2 k2 s^2 + 1/6 k3 s^3 7 | 8 | from math import * 9 | 10 | def eval_cubic(a, b, c, d, x): 11 | return ((d * x + c) * x + b) * x + a 12 | 13 | # integrate over s = [0, 1] 14 | def int_3spiro_poly(ks, n): 15 | x, y = 0, 0 16 | th = 0 17 | ds = 1.0 / n 18 | th1, th2, th3, th4 = ks[0], .5 * ks[1], (1./6) * ks[2], (1./24) * ks[3] 19 | k0, k1, k2, k3 = ks[0] * ds, ks[1] * ds, ks[2] * ds, ks[3] * ds 20 | s = 0 21 | result = [(x, y)] 22 | for i in range(n): 23 | sm = s + 0.5 * ds 24 | th = sm * eval_cubic(th1, th2, th3, th4, sm) 25 | cth = cos(th) 26 | sth = sin(th) 27 | 28 | km0 = ((1./6 * k3 * sm + .5 * k2) * sm + k1) * sm + k0 29 | km1 = ((.5 * k3 * sm + k2) * sm + k1) * ds 30 | km2 = (k3 * sm + k2) * ds * ds 31 | km3 = k3 * ds * ds * ds 32 | #print km0, km1, km2, km3 33 | u = 1 - km0 * km0 / 24 34 | v = km1 / 24 35 | 36 | u = 1 - km0 * km0 / 24 + (km0 ** 4 - 4 * km0 * km2 - 3 * km1 * km1) / 1920 37 | v = km1 / 24 + (km3 - 6 * km0 * km0 * km1) / 1920 38 | 39 | x += cth * u - sth * v 40 | y += cth * v + sth * u 41 | result.append((ds * x, ds * y)) 42 | 43 | s += ds 44 | 45 | return result 46 | 47 | def integ_chord(k, n = 64): 48 | ks = (k[0] * .5, k[1] * .25, k[2] * .125, k[3] * .0625) 49 | xp, yp = int_3spiro_poly(ks, n)[-1] 50 | ks = (k[0] * -.5, k[1] * .25, k[2] * -.125, k[3] * .0625) 51 | xm, ym = int_3spiro_poly(ks, n)[-1] 52 | dx, dy = .5 * (xp + xm), .5 * (yp + ym) 53 | return hypot(dx, dy), atan2(dy, dx) 54 | 55 | # Return th0, th1, k0, k1 for given params 56 | def calc_thk(ks): 57 | chord, ch_th = integ_chord(ks) 58 | th0 = ch_th - (-.5 * ks[0] + .125 * ks[1] - 1./48 * ks[2] + 1./384 * ks[3]) 59 | th1 = (.5 * ks[0] + .125 * ks[1] + 1./48 * ks[2] + 1./384 * ks[3]) - ch_th 60 | k0 = chord * (ks[0] - .5 * ks[1] + .125 * ks[2] - 1./48 * ks[3]) 61 | k1 = chord * (ks[0] + .5 * ks[1] + .125 * ks[2] + 1./48 * ks[3]) 62 | #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 63 | return th0, th1, k0, k1 64 | 65 | def calc_k1k2(ks): 66 | chord, ch_th = integ_chord(ks) 67 | k1l = chord * chord * (ks[1] - .5 * ks[2] + .125 * ks[3]) 68 | k1r = chord * chord * (ks[1] + .5 * ks[2] + .125 * ks[3]) 69 | k2l = chord * chord * chord * (ks[2] - .5 * ks[3]) 70 | k2r = chord * chord * chord * (ks[2] + .5 * ks[3]) 71 | return k1l, k1r, k2l, k2r 72 | 73 | def plot(ks): 74 | ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625) 75 | pside = int_3spiro_poly(ksp, 64) 76 | ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625) 77 | mside = int_3spiro_poly(ksm, 64) 78 | mside.reverse() 79 | for i in range(len(mside)): 80 | mside[i] = (-mside[i][0], -mside[i][1]) 81 | pts = mside + pside[1:] 82 | cmd = "moveto" 83 | for j in range(len(pts)): 84 | x, y = pts[j] 85 | print 306 + 300 * x, 400 + 300 * y, cmd 86 | cmd = "lineto" 87 | print "stroke" 88 | x, y = pts[0] 89 | print 306 + 300 * x, 400 + 300 * y, "moveto" 90 | x, y = pts[-1] 91 | print 306 + 300 * x, 400 + 300 * y, "lineto .5 setlinewidth stroke" 92 | print "showpage" 93 | 94 | def solve_3spiro(th0, th1, k0, k1): 95 | ks = [0, 0, 0, 0] 96 | for i in range(5): 97 | th0_a, th1_a, k0_a, k1_a = calc_thk(ks) 98 | dth0 = th0 - th0_a 99 | dth1 = th1 - th1_a 100 | dk0 = k0 - k0_a 101 | dk1 = k1 - k1_a 102 | ks[0] += (dth0 + dth1) * 1.5 + (dk0 + dk1) * -.25 103 | ks[1] += (dth1 - dth0) * 15 + (dk0 - dk1) * 1.5 104 | ks[2] += (dth0 + dth1) * -12 + (dk0 + dk1) * 6 105 | ks[3] += (dth0 - dth1) * 360 + (dk1 - dk0) * 60 106 | #print '% ks =', ks 107 | return ks 108 | 109 | def iter_spline(pts, ths, ks): 110 | pass 111 | 112 | def solve_vee(): 113 | kss = [] 114 | for i in range(10): 115 | kss.append([0, 0, 0, 0]) 116 | thl = [0] * len(kss) 117 | thr = [0] * len(kss) 118 | k0l = [0] * len(kss) 119 | k0r = [0] * len(kss) 120 | k1l = [0] * len(kss) 121 | k1r = [0] * len(kss) 122 | k2l = [0] * len(kss) 123 | k2r = [0] * len(kss) 124 | for i in range(10): 125 | for j in range(len(kss)): 126 | thl[j], thr[j], k0l[j], k0r[j] = calc_thk(kss[j]) 127 | k0l[j], k1r[j], k2l[j], k2r[j] = calc_k1k2(kss[j]) 128 | for j in range(len(kss) - 1): 129 | dth = thl[j + 1] + thr[j] 130 | if j == 5: dth += .1 131 | dk0 = k0l[j + 1] - k0r[j] 132 | dk1 = k1l[j + 1] - k1r[j] 133 | dk2 = k2l[j + 1] - k2r[j] 134 | 135 | 136 | if __name__ == '__main__': 137 | k0 = pi * 3 138 | ks = [0, k0, -2 * k0, 0] 139 | ks = [0, 0, 0, 0.01] 140 | #plot(ks) 141 | thk = calc_thk(ks) 142 | print '%', thk 143 | 144 | ks = solve_3spiro(0, 0, 0, 0.001) 145 | print '% thk =', calc_thk(ks) 146 | #plot(ks) 147 | print '%', ks 148 | print calc_k1k2(ks) 149 | -------------------------------------------------------------------------------- /curves/polymat-bad.py: -------------------------------------------------------------------------------- 1 | from Numeric import * 2 | import LinearAlgebra as la 3 | import sys 4 | 5 | n = 15 6 | m = zeros(((n + 1) * 4, (n + 1) * 4), Float) 7 | for i in range(n): 8 | m[4 * i + 2][4 * i + 0] = .5 9 | m[4 * i + 2][4 * i + 1] = -1./12 10 | m[4 * i + 2][4 * i + 2] = 1./48 11 | m[4 * i + 2][4 * i + 3] = -1./480 12 | m[4 * i + 2][4 * i + 4] = .5 13 | m[4 * i + 2][4 * i + 5] = 1./12 14 | m[4 * i + 2][4 * i + 6] = 1./48 15 | m[4 * i + 2][4 * i + 7] = 1./480 16 | 17 | m[4 * i + 3][4 * i + 0] = 1 18 | m[4 * i + 3][4 * i + 1] = .5 19 | m[4 * i + 3][4 * i + 2] = .125 20 | m[4 * i + 3][4 * i + 3] = 1./48 21 | m[4 * i + 3][4 * i + 4] = -1 22 | m[4 * i + 3][4 * i + 5] = .5 23 | m[4 * i + 3][4 * i + 6] = -.125 24 | m[4 * i + 3][4 * i + 7] = 1./48 25 | 26 | m[4 * i + 4][4 * i + 0] = 0 27 | m[4 * i + 4][4 * i + 1] = 1 28 | m[4 * i + 4][4 * i + 2] = .5 29 | m[4 * i + 4][4 * i + 3] = .125 30 | m[4 * i + 4][4 * i + 4] = 0 31 | m[4 * i + 4][4 * i + 5] = -1 32 | m[4 * i + 4][4 * i + 6] = .5 33 | m[4 * i + 4][4 * i + 7] = -.125 34 | 35 | m[4 * i + 5][4 * i + 0] = 0 36 | m[4 * i + 5][4 * i + 1] = 0 37 | m[4 * i + 5][4 * i + 2] = 1 38 | m[4 * i + 5][4 * i + 3] = .5 39 | m[4 * i + 5][4 * i + 4] = 0 40 | m[4 * i + 5][4 * i + 5] = 0 41 | m[4 * i + 5][4 * i + 6] = -1 42 | m[4 * i + 5][4 * i + 7] = .5 43 | 44 | m[n * 4 + 2][2] = 1 45 | m[n * 4 + 3][3] = 1 46 | 47 | m[0][n * 4 + 2] = 1 48 | m[1][n * 4 + 3] = 1 49 | 50 | def printarr(m): 51 | for j in range(n * 4 + 4): 52 | for i in range(n * 4 + 4): 53 | print '%6.1f' % m[j][i], 54 | print '' 55 | 56 | sys.output_line_width = 160 57 | #print array2string(m, precision = 3) 58 | mi = la.inverse(m) 59 | #printarr(mi) 60 | print '' 61 | for j in range(n + 1): 62 | for k in range(4): 63 | print '%7.2f' % mi[j * 4 + k][(n / 2) * 4 + 2], 64 | print '' 65 | -------------------------------------------------------------------------------- /curves/polymat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from math import * 3 | 4 | from Numeric import * 5 | import LinearAlgebra as la 6 | 7 | import poly3 8 | 9 | n = 15 10 | m = zeros(((n + 1) * 4, (n + 1) * 4), Float) 11 | for i in range(n): 12 | m[4 * i + 2][4 * i + 0] = .5 13 | m[4 * i + 2][4 * i + 1] = -1./12 14 | m[4 * i + 2][4 * i + 2] = 1./48 15 | m[4 * i + 2][4 * i + 3] = -1./480 16 | m[4 * i + 2][4 * i + 4] = .5 17 | m[4 * i + 2][4 * i + 5] = 1./12 18 | m[4 * i + 2][4 * i + 6] = 1./48 19 | m[4 * i + 2][4 * i + 7] = 1./480 20 | 21 | m[4 * i + 3][4 * i + 0] = 1 22 | m[4 * i + 3][4 * i + 1] = .5 23 | m[4 * i + 3][4 * i + 2] = .125 24 | m[4 * i + 3][4 * i + 3] = 1./48 25 | m[4 * i + 3][4 * i + 4] = -1 26 | m[4 * i + 3][4 * i + 5] = .5 27 | m[4 * i + 3][4 * i + 6] = -.125 28 | m[4 * i + 3][4 * i + 7] = 1./48 29 | 30 | m[4 * i + 4][4 * i + 0] = 0 31 | m[4 * i + 4][4 * i + 1] = 1 32 | m[4 * i + 4][4 * i + 2] = .5 33 | m[4 * i + 4][4 * i + 3] = .125 34 | m[4 * i + 4][4 * i + 4] = 0 35 | m[4 * i + 4][4 * i + 5] = -1 36 | m[4 * i + 4][4 * i + 6] = .5 37 | m[4 * i + 4][4 * i + 7] = -.125 38 | 39 | m[4 * i + 5][4 * i + 0] = 0 40 | m[4 * i + 5][4 * i + 1] = 0 41 | m[4 * i + 5][4 * i + 2] = 1 42 | m[4 * i + 5][4 * i + 3] = .5 43 | m[4 * i + 5][4 * i + 4] = 0 44 | m[4 * i + 5][4 * i + 5] = 0 45 | m[4 * i + 5][4 * i + 6] = -1 46 | m[4 * i + 5][4 * i + 7] = .5 47 | 48 | m[n * 4 + 2][2] = 1 49 | m[n * 4 + 3][3] = 1 50 | 51 | m[0][n * 4 + 2] = 1 52 | m[1][n * 4 + 3] = 1 53 | 54 | def printarr(m): 55 | for j in range(n * 4 + 4): 56 | for i in range(n * 4 + 4): 57 | print '%6.1f' % m[j][i], 58 | print '' 59 | 60 | sys.output_line_width = 160 61 | #print array2string(m, precision = 3) 62 | mi = la.inverse(m) 63 | #printarr(mi) 64 | 65 | # fit arc to pts (0, 0), (x, y), and (1, 0), return th tangent to 66 | # arc at (x, y) 67 | def fit_arc(x, y): 68 | th = atan2(y - 2 * x * y, y * y + x - x * x) 69 | return th 70 | 71 | def mod_2pi(th): 72 | u = th / (2 * pi) 73 | return 2 * pi * (u - floor(u + 0.5)) 74 | 75 | def plot_path(path, th, k): 76 | if path[0][2] == '{': closed = 0 77 | else: closed = 1 78 | j = 0 79 | cmd = 'moveto' 80 | for i in range(len(path) + closed - 1): 81 | i1 = (i + 1) % len(path) 82 | x0, y0, t0 = path[i] 83 | x1, y1, t1 = path[i1] 84 | j1 = (j + 1) % len(th) 85 | th0 = th[j] 86 | k0 = k[j] 87 | th1 = th[j1] 88 | k1 = k[j1] 89 | chord_th = atan2(y1 - y0, x1 - x0) 90 | chord_len = hypot(y1 - y0, x1 - x0) 91 | ks = poly3.solve_3spiro(mod_2pi(chord_th - th0), 92 | mod_2pi(th1 - chord_th), 93 | k0 * chord_len, k1 * chord_len) 94 | ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625) 95 | pside = poly3.int_3spiro_poly(ksp, 64) 96 | ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625) 97 | mside = poly3.int_3spiro_poly(ksm, 64) 98 | mside.reverse() 99 | for i in range(len(mside)): 100 | mside[i] = (-mside[i][0], -mside[i][1]) 101 | pts = mside + pside[1:] 102 | rot = chord_th - atan2(pts[-1][1] - pts[0][1], pts[-1][0] - pts[0][0]) 103 | scale = hypot(x1 - x0, y1 - y0) / hypot(pts[-1][1] - pts[0][1], pts[-1][0] - pts[0][0]) 104 | u, v = scale * cos(rot), scale * sin(rot) 105 | xt = x0 - u * pts[0][0] + v * pts[0][1] 106 | yt = y0 - u * pts[0][1] - v * pts[0][0] 107 | for x, y in pts: 108 | print xt + u * x - v * y, yt + u * y + v * x, cmd 109 | cmd = 'lineto' 110 | if t1 == 'v': 111 | j += 2 112 | else: 113 | j += 1 114 | print 'stroke' 115 | if 0: 116 | j = 0 117 | for i in range(len(path)): 118 | x0, y0, t0 = path[i] 119 | print 'gsave', x0, y0, 'translate' 120 | print ' ', th[j] * 180 / pi, 'rotate' 121 | print ' -50 0 moveto 50 0 lineto stroke' 122 | print 'grestore' 123 | j += 1 124 | 125 | def plot_ks(path, th, k): 126 | if path[0][2] == '{': closed = 0 127 | else: closed = 1 128 | j = 0 129 | cmd = 'moveto' 130 | xo = 36 131 | yo = 550 132 | xscale = .5 133 | yscale = 2000 134 | x = xo 135 | for i in range(len(path) + closed - 1): 136 | i1 = (i + 1) % len(path) 137 | x0, y0, t0 = path[i] 138 | x1, y1, t1 = path[i1] 139 | j1 = (j + 1) % len(th) 140 | th0 = th[j] 141 | k0 = k[j] 142 | th1 = th[j1] 143 | k1 = k[j1] 144 | chord_th = atan2(y1 - y0, x1 - x0) 145 | chord_len = hypot(y1 - y0, x1 - x0) 146 | ks = poly3.solve_3spiro(mod_2pi(chord_th - th0), 147 | mod_2pi(th1 - chord_th), 148 | k0 * chord_len, k1 * chord_len) 149 | ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625) 150 | pside = poly3.int_3spiro_poly(ksp, 64) 151 | ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625) 152 | mside = poly3.int_3spiro_poly(ksm, 64) 153 | print '%', x0, y0, k0, k1 154 | print "% ks = ", ks 155 | l = 2 * chord_len / hypot(pside[-1][0] + mside[-1][0], pside[-1][1] + mside[-1][1]) 156 | for i in range(100): 157 | s = .01 * i - 0.5 158 | kp = poly3.eval_cubic(ks[0], ks[1], .5 * ks[2], 1./6 * ks[3], s) 159 | kp /= l 160 | print x + xscale * l * (s + .5), yo + yscale * kp, cmd 161 | cmd = 'lineto' 162 | x += xscale * l 163 | if t1 == 'v': 164 | j += 2 165 | else: 166 | j += 1 167 | print 'stroke' 168 | print xo, yo, 'moveto', x, yo, 'lineto stroke' 169 | 170 | def make_error_vec(path, th, k): 171 | if path[0][2] == '{': closed = 0 172 | else: closed = 1 173 | n = len(path) 174 | v = zeros(n * 2, Float) 175 | j = 0 176 | for i in range(len(path) + closed - 1): 177 | i1 = (i + 1) % len(path) 178 | x0, y0, t0 = path[i] 179 | x1, y1, t1 = path[i1] 180 | j1 = (j + 1) % len(th) 181 | th0 = th[j] 182 | k0 = k[j] 183 | th1 = th[j1] 184 | k1 = k[j1] 185 | chord_th = atan2(y1 - y0, x1 - x0) 186 | chord_len = hypot(y1 - y0, x1 - x0) 187 | ks = poly3.solve_3spiro(mod_2pi(chord_th - th0), 188 | mod_2pi(th1 - chord_th), 189 | k0 * chord_len, k1 * chord_len) 190 | ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625) 191 | pside = poly3.int_3spiro_poly(ksp, 64) 192 | ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625) 193 | mside = poly3.int_3spiro_poly(ksm, 64) 194 | l = chord_len / hypot(pside[-1][0] + mside[-1][0], pside[-1][1] + mside[-1][1]) 195 | k1l = (ks[1] - .5 * ks[2] + .125 * ks[3]) / (l * l) 196 | k1r = (ks[1] + .5 * ks[2] + .125 * ks[3]) / (l * l) 197 | k2l = (ks[2] - .5 * ks[3]) / (l * l * l) 198 | k2r = (ks[2] + .5 * ks[3]) / (l * l * l) 199 | 200 | if t0 == 'o' or t0 == '[' or t0 == 'v': 201 | v[2 * j] -= k1l 202 | v[2 * j + 1] -= k2l 203 | elif t0 == 'c': 204 | v[2 * j + 1] += k2l 205 | 206 | if t1 == 'o' or t1 == ']' or t1 == 'v': 207 | v[2 * j1] += k1r 208 | v[2 * j1 + 1] += k2r 209 | elif t1 == 'c': 210 | v[2 * j1] += k2r 211 | 212 | print "% left k'", k1l, "k''", k2l, "right k'", k1r, "k''", k2r 213 | if t1 == 'v': 214 | j += 2 215 | else: 216 | j += 1 217 | print '% error vector:' 218 | for i in range(n): 219 | print '% ', v[2 * i], v[2 * i + 1] 220 | return v 221 | 222 | def add_k1l(m, row, col, col1, l, s): 223 | l2 = l * l 224 | m[row][2 * col] += s * 36 / l2 225 | m[row][2 * col + 1] -= s * 9 / l 226 | m[row][2 * col1] += s * 24 / l2 227 | m[row][2 * col1 + 1] += s * 3 / l 228 | 229 | def add_k1r(m, row, col, col1, l, s): 230 | l2 = l * l 231 | m[row][2 * col] += s * 24 / l2 232 | m[row][2 * col + 1] -= s * 3 / l 233 | m[row][2 * col1] += s * 36 / l2 234 | m[row][2 * col1 + 1] += s * 9 / l 235 | 236 | def add_k2l(m, row, col, col1, l, s): 237 | l2 = l * l 238 | l3 = l2 * l 239 | m[row][2 * col] -= s * 192 / l3 240 | m[row][2 * col + 1] += s * 36 / l2 241 | m[row][2 * col1] -= s * 168 / l3 242 | m[row][2 * col1 + 1] -= s * 24 / l2 243 | 244 | def add_k2r(m, row, col, col1, l, s): 245 | l2 = l * l 246 | l3 = l2 * l 247 | m[row][2 * col] += s * 168 / l3 248 | m[row][2 * col + 1] -= s * 24 / l2 249 | m[row][2 * col1] += s * 192 / l3 250 | m[row][2 * col1 + 1] += s * 36 / l2 251 | 252 | def make_matrix(path, th, k): 253 | if path[0][2] == '{': closed = 0 254 | else: closed = 1 255 | n = len(path) 256 | m = zeros((n * 2, n * 2), Float) 257 | j = 0 258 | for i in range(len(path) + closed - 1): 259 | i1 = (i + 1) % len(path) 260 | x0, y0, t0 = path[i] 261 | x1, y1, t1 = path[i1] 262 | j1 = (j + 1) % len(th) 263 | th0 = th[j] 264 | k0 = k[j] 265 | th1 = th[j1] 266 | k1 = k[j1] 267 | chord_th = atan2(y1 - y0, x1 - x0) 268 | chord_len = hypot(y1 - y0, x1 - x0) 269 | ks = poly3.solve_3spiro(mod_2pi(chord_th - th0), 270 | mod_2pi(th1 - chord_th), 271 | k0 * chord_len, k1 * chord_len) 272 | ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625) 273 | pside = poly3.int_3spiro_poly(ksp, 64) 274 | ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625) 275 | mside = poly3.int_3spiro_poly(ksm, 64) 276 | l = chord_len / hypot(pside[-1][0] + mside[-1][0], pside[-1][1] + mside[-1][1]) 277 | 278 | if t0 == 'o' or t0 == '[' or t0 == 'v': 279 | add_k1l(m, 2 * j, j, j1, l, -1) 280 | add_k2l(m, 2 * j + 1, j, j1, l, -1) 281 | elif t0 == 'c': 282 | add_k2l(m, 2 * j + 1, j, j1, l, 1) 283 | 284 | if t1 == 'o' or t1 == ']' or t1 == 'v': 285 | add_k1r(m, 2 * j1, j, j1, l, 1) 286 | add_k2r(m, 2 * j1 + 1, j, j1, l, 1) 287 | elif t1 == 'c': 288 | add_k2r(m, 2 * j1, j, j1, l, 1) 289 | 290 | if t1 == 'v': 291 | j += 2 292 | else: 293 | j += 1 294 | return m 295 | 296 | def solve(path): 297 | if path[0][2] == '{': closed = 0 298 | else: closed = 1 299 | dxs = [] 300 | dys = [] 301 | chords = [] 302 | for i in range(len(path) - 1): 303 | dxs.append(path[i + 1][0] - path[i][0]) 304 | dys.append(path[i + 1][1] - path[i][1]) 305 | chords.append(hypot(dxs[-1], dys[-1])) 306 | nominal_th = [] 307 | nominal_k = [] 308 | if not closed: 309 | nominal_th.append(atan2(dys[0], dxs[0])) 310 | nominal_k.append(0) 311 | for i in range(1 - closed, len(path) - 1 + closed): 312 | x0, y0, t0 = path[(i + len(path) - 1) % len(path)] 313 | x1, y1, t1 = path[i] 314 | x2, y2, t2 = path[(i + 1) % len(path)] 315 | dx = float(x2 - x0) 316 | dy = float(y2 - y0) 317 | ir2 = dx * dx + dy * dy 318 | x = ((x1 - x0) * dx + (y1 - y0) * dy) / ir2 319 | y = ((y1 - y0) * dx - (x1 - x0) * dy) / ir2 320 | th = fit_arc(x, y) + atan2(dy, dx) 321 | bend_angle = mod_2pi(atan2(y2 - y1, x2 - x1) - atan2(y1 - y0, x1 - x0)) 322 | k = 2 * bend_angle/(hypot(y2 - y1, x2 - x1) + hypot(y1 - y0, x1 - x0)) 323 | print '% bend angle', bend_angle, 'k', k 324 | if t1 == ']': 325 | th = atan2(y1 - y0, x1 - x0) 326 | k = 0 327 | elif t1 == '[': 328 | th = atan2(y2 - y1, x2 - x1) 329 | k = 0 330 | nominal_th.append(th) 331 | nominal_k.append(k) 332 | if not closed: 333 | nominal_th.append(atan2(dys[-1], dxs[-1])) 334 | nominal_k.append(0) 335 | print '%', nominal_th 336 | print '0 0 1 setrgbcolor .5 setlinewidth' 337 | plot_path(path, nominal_th, nominal_k) 338 | plot_ks(path, nominal_th, nominal_k) 339 | th = nominal_th[:] 340 | k = nominal_k[:] 341 | n = 8 342 | for i in range(n): 343 | ev = make_error_vec(path, th, k) 344 | m = make_matrix(path, th, k) 345 | #print m 346 | #print 'inverse:' 347 | #print la.inverse(m) 348 | v = dot(la.inverse(m), ev) 349 | #print v 350 | for j in range(len(path)): 351 | th[j] += 1. * v[2 * j] 352 | k[j] -= 1. * .5 * v[2 * j + 1] 353 | if i == n - 1: 354 | print '0 0 0 setrgbcolor 1 setlinewidth' 355 | elif i == 0: 356 | print '1 0 0 setrgbcolor' 357 | elif i == 1: 358 | print '0 0.5 0 setrgbcolor' 359 | elif i == 2: 360 | print '0.3 0.3 0.3 setrgbcolor' 361 | plot_path(path, th, k) 362 | plot_ks(path, th, k) 363 | print '% th:', th 364 | print '% k:', k 365 | 366 | path = [(100, 100, 'o'), (200, 250, 'o'), (350, 225, 'o'), 367 | (450, 350, 'c'), (450, 200, 'o'), (300, 100, 'o')] 368 | if 0: 369 | path = [(100, 480, '['), (100, 300, ']'), (300, 100, 'o'), 370 | (500, 300, '['), (500, 480, ']'), (480, 500, '['), 371 | (120, 500, ']')] 372 | 373 | 374 | path = [(100, 480, ']'), (100, 120, '['), 375 | (120, 100, ']'), (480, 100, '['), 376 | (500, 120, ']'), (500, 480, '['), 377 | (480, 500, ']'), (120, 500, '[')] 378 | 379 | path = [(100, 120, '['), (120, 100, ']'), 380 | (140, 100, 'o'), (160, 100, 'o'), (180, 100, 'o'), (200, 100, 'o'), 381 | (250, 250, 'o'), 382 | (100, 200, 'o'), (100, 180, 'o'), (100, 160, 'o'), (100, 140, 'o')] 383 | 384 | path = [(100, 350, 'o'), (225, 350, 'o'), (350, 450, 'o'), 385 | (450, 400, 'o'), (315, 205, 'o'), (300, 200, 'o'), 386 | (285, 205, 'o')] 387 | 388 | if 0: 389 | path = [] 390 | path.append((350, 600, 'c')) 391 | path.append((50, 450, 'c')) 392 | for i in range(10): 393 | path.append((50 + i * 30, 300 - i * 5, 'c')) 394 | for i in range(11): 395 | path.append((350 + i * 30, 250 + i * 5, 'c')) 396 | path.append((650, 450, 'c')) 397 | 398 | solve(path) 399 | print 'showpage' 400 | -------------------------------------------------------------------------------- /curves/tocubic.py: -------------------------------------------------------------------------------- 1 | # Some code to convert arbitrary curves to high quality cubics. 2 | 3 | # Some conventions: points are (x, y) pairs. Cubic Bezier segments are 4 | # lists of four points. 5 | 6 | import sys 7 | 8 | from math import * 9 | 10 | import pcorn 11 | 12 | def pt_wsum(points, wts): 13 | x, y = 0, 0 14 | for i in range(len(points)): 15 | x += points[i][0] * wts[i] 16 | y += points[i][1] * wts[i] 17 | return x, y 18 | 19 | # Very basic spline primitives 20 | def bz_eval(bz, t): 21 | degree = len(bz) - 1 22 | mt = 1 - t 23 | if degree == 3: 24 | return pt_wsum(bz, [mt * mt * mt, 3 * mt * mt * t, 3 * mt * t * t, t * t * t]) 25 | elif degree == 2: 26 | return pt_wsum(bz, [mt * mt, 2 * mt * t, t * t]) 27 | elif degree == 1: 28 | return pt_wsum(bz, [mt, t]) 29 | 30 | def bz_deriv(bz): 31 | degree = len(bz) - 1 32 | return [(degree * (bz[i + 1][0] - bz[i][0]), degree * (bz[i + 1][1] - bz[i][1])) for i in range(degree)] 33 | 34 | def bz_arclength(bz, n = 10): 35 | # We're just going to integrate |z'| over the parameter [0..1]. 36 | # The integration algorithm here is eqn 4.1.14 from NRC2, and is 37 | # chosen for simplicity. Likely adaptive and/or higher-order 38 | # algorithms would be better, but this should be good enough. 39 | # Convergence should be quartic in n. 40 | wtarr = (3./8, 7./6, 23./24) 41 | dt = 1./n 42 | s = 0 43 | dbz = bz_deriv(bz) 44 | for i in range(0, n + 1): 45 | if i < 3: 46 | wt = wtarr[i] 47 | elif i > n - 3: 48 | wt = wtarr[n - i] 49 | else: 50 | wt = 1. 51 | dx, dy = bz_eval(dbz, i * dt) 52 | ds = hypot(dx, dy) 53 | s += wt * ds 54 | return s * dt 55 | 56 | # One step of 4th-order Runge-Kutta numerical integration - update y in place 57 | def rk4(y, dydx, x, h, derivs): 58 | hh = h * .5 59 | h6 = h * (1./6) 60 | xh = x + hh 61 | yt = [] 62 | for i in range(len(y)): 63 | yt.append(y[i] + hh * dydx[i]) 64 | dyt = derivs(xh, yt) 65 | for i in range(len(y)): 66 | yt[i] = y[i] + hh * dyt[i] 67 | dym = derivs(xh, yt) 68 | for i in range(len(y)): 69 | yt[i] = y[i] + h * dym[i] 70 | dym[i] += dyt[i] 71 | dyt = derivs(x + h, yt) 72 | for i in range(len(y)): 73 | y[i] += h6 * (dydx[i] + dyt[i] + 2 * dym[i]) 74 | 75 | def bz_arclength_rk4(bz, n = 10): 76 | dbz = bz_deriv(bz) 77 | def arclength_deriv(x, ys): 78 | dx, dy = bz_eval(dbz, x) 79 | return [hypot(dx, dy)] 80 | dt = 1./n 81 | t = 0 82 | ys = [0] 83 | for i in range(n): 84 | dydx = arclength_deriv(t, ys) 85 | rk4(ys, dydx, t, dt, arclength_deriv) 86 | t += dt 87 | return ys[0] 88 | 89 | # z0 and z1 are start and end points, resp. 90 | # th0 and th1 are the initial and final tangents, measured in the 91 | # direction of the curve. 92 | # aab is a/(a + b), where a and b are the lengths of the bezier "arms" 93 | def fit_cubic_arclen(z0, z1, arclen, th0, th1, aab): 94 | chord = hypot(z1[0] - z0[0], z1[1] - z0[1]) 95 | cth0, sth0 = cos(th0), sin(th0) 96 | cth1, sth1 = -cos(th1), -sin(th1) 97 | armlen = .66667 * chord 98 | darmlen = 1e-6 * armlen 99 | for i in range(10): 100 | a = armlen * aab 101 | b = armlen - a 102 | bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), 103 | (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] 104 | actual_s = bz_arclength_rk4(bz) 105 | if (abs(arclen - actual_s) < 1e-12): 106 | break 107 | a = (armlen + darmlen) * aab 108 | b = (armlen + darmlen) - a 109 | bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), 110 | (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] 111 | actual_s2 = bz_arclength_rk4(bz) 112 | ds = (actual_s2 - actual_s) / darmlen 113 | #print '% armlen = ', armlen 114 | if ds == 0: 115 | break 116 | armlen += (arclen - actual_s) / ds 117 | a = armlen * aab 118 | b = armlen - a 119 | bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), 120 | (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] 121 | return bz 122 | 123 | def mod_2pi(th): 124 | u = th / (2 * pi) 125 | return 2 * pi * (u - floor(u + 0.5)) 126 | 127 | def measure_bz(bz, arclen, th_fn, n = 1000): 128 | dt = 1./n 129 | dbz = bz_deriv(bz) 130 | s = 0 131 | score = 0 132 | for i in range(n): 133 | dx, dy = bz_eval(dbz, (i + .5) * dt) 134 | ds = dt * hypot(dx, dy) 135 | s += ds 136 | score += ds * (mod_2pi(atan2(dy, dx) - th_fn(s)) ** 2) 137 | return score 138 | 139 | def measure_bz_rk4(bz, arclen, th_fn, n = 10): 140 | dbz = bz_deriv(bz) 141 | def measure_derivs(x, ys): 142 | dx, dy = bz_eval(dbz, x) 143 | ds = hypot(dx, dy) 144 | s = ys[0] 145 | dscore = ds * (mod_2pi(atan2(dy, dx) - th_fn(s)) ** 2) 146 | return [ds, dscore] 147 | dt = 1./n 148 | t = 0 149 | ys = [0, 0] 150 | for i in range(n): 151 | dydx = measure_derivs(t, ys) 152 | rk4(ys, dydx, t, dt, measure_derivs) 153 | t += dt 154 | return ys[1] 155 | 156 | # th_fn() is a function that takes an arclength from the start point, and 157 | # returns an angle - thus th_fn(0) and th_fn(arclen) are the initial and 158 | # final tangents. 159 | # z0, z1, and arclen are as fit_cubic_arclen 160 | def fit_cubic(z0, z1, arclen, th_fn, fast = 1): 161 | chord = hypot(z1[0] - z0[0], z1[1] - z0[1]) 162 | if (arclen < 1.000001 * chord): 163 | return [z0, z1], 0 164 | th0 = th_fn(0) 165 | th1 = th_fn(arclen) 166 | imax = 4 167 | jmax = 10 168 | aabmin = 0 169 | aabmax = 1. 170 | if fast: 171 | imax = 1 172 | jmax = 0 173 | for i in range(imax): 174 | for j in range(jmax + 1): 175 | if jmax == 0: 176 | aab = 0.5 * (aabmin + aabmax) 177 | else: 178 | aab = aabmin + (aabmax - aabmin) * j / jmax 179 | bz = fit_cubic_arclen(z0, z1, arclen, th0, th1, aab) 180 | score = measure_bz_rk4(bz, arclen, th_fn) 181 | print '% aab =', aab, 'score =', score 182 | sys.stdout.flush() 183 | if j == 0 or score < best_score: 184 | best_score = score 185 | best_aab = aab 186 | best_bz = bz 187 | daab = .06 * (aabmax - aabmin) 188 | aabmin = max(0, best_aab - daab) 189 | aabmax = min(1, best_aab + daab) 190 | print '%--- best_aab =', best_aab 191 | return best_bz, best_score 192 | 193 | def plot_prolog(): 194 | print '%!PS' 195 | print '/m { moveto } bind def' 196 | print '/l { lineto } bind def' 197 | print '/c { curveto } bind def' 198 | print '/z { closepath } bind def' 199 | 200 | def plot_bz(bz, z0, scale, do_moveto = True): 201 | x0, y0 = z0 202 | if do_moveto: 203 | print bz[0][0] * scale + x0, bz[0][1] * scale + y0, 'm' 204 | if len(bz) == 4: 205 | x1, y1 = bz[1][0] * scale + x0, bz[1][1] * scale + y0 206 | x2, y2 = bz[2][0] * scale + x0, bz[2][1] * scale + y0 207 | x3, y3 = bz[3][0] * scale + x0, bz[3][1] * scale + y0 208 | print x1, y1, x2, y2, x3, y3, 'c' 209 | elif len(bz) == 2: 210 | print bz[1][0] * scale + x0, bz[1][1] * scale + y0, 'l' 211 | 212 | def test_bz_arclength(): 213 | bz = [(0, 0), (.5, 0), (1, 0.5), (1, 1)] 214 | ans = bz_arclength_rk4(bz, 2048) 215 | last = 1 216 | lastrk = 1 217 | for i in range(3, 11): 218 | n = 1 << i 219 | err = bz_arclength(bz, n) - ans 220 | err_rk = bz_arclength_rk4(bz, n) - ans 221 | print n, err, last / err, err_rk, lastrk / err_rk 222 | last = err 223 | lastrk = err_rk 224 | 225 | def test_fit_cubic_arclen(): 226 | th = pi / 4 227 | arclen = th / sin(th) 228 | bz = fit_cubic_arclen((0, 0), (1, 0), arclen, th, th, .5) 229 | print '%', bz 230 | plot_bz(bz, (100, 400), 500) 231 | print 'stroke' 232 | print 'showpage' 233 | 234 | # -- cornu fitting 235 | 236 | import cornu 237 | 238 | def cornu_to_cubic(t0, t1): 239 | def th_fn(s): 240 | return (s + t0) ** 2 241 | y0, x0 = cornu.eval_cornu(t0) 242 | y1, x1 = cornu.eval_cornu(t1) 243 | bz, score = fit_cubic((x0, y0), (x1, y1), t1 - t0, th_fn, 0) 244 | return bz, score 245 | 246 | def test_draw_cornu(): 247 | plot_prolog() 248 | thresh = 1e-6 249 | print '/ss 1.5 def' 250 | print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' 251 | s0 = 0 252 | imax = 200 253 | x0, y0, scale = 36, 100, 500 254 | bzs = [] 255 | for i in range(1, imax): 256 | s = sqrt(i * .1) 257 | bz, score = cornu_to_cubic(s0, s) 258 | if score > (s - s0) * thresh or i == imax - 1: 259 | plot_bz(bz, (x0, y0), scale, s0 == 0) 260 | bzs.append(bz) 261 | s0 = s 262 | print 'stroke' 263 | for i in range(len(bzs)): 264 | bz = bzs[i] 265 | bx0, by0 = x0 + bz[0][0] * scale, y0 + bz[0][1] * scale 266 | bx1, by1 = x0 + bz[1][0] * scale, y0 + bz[1][1] * scale 267 | bx2, by2 = x0 + bz[2][0] * scale, y0 + bz[2][1] * scale 268 | bx3, by3 = x0 + bz[3][0] * scale, y0 + bz[3][1] * scale 269 | print 'gsave 0 0 1 setrgbcolor .5 setlinewidth' 270 | print bx0, by0, 'moveto', bx1, by1, 'lineto stroke' 271 | print bx2, by2, 'moveto', bx3, by3, 'lineto stroke' 272 | print 'grestore' 273 | print 'gsave', bx0, by0, 'translate circle fill grestore' 274 | print 'gsave', bx1, by1, 'translate .5 dup scale circle fill grestore' 275 | print 'gsave', bx2, by2, 'translate .5 dup scale circle fill grestore' 276 | print 'gsave', bx3, by3, 'translate circle fill grestore' 277 | 278 | # -- fitting of piecewise cornu curves 279 | 280 | def pcorn_segment_to_bzs_optim_inner(curve, s0, s1, thresh, nmax = None): 281 | result = [] 282 | if s0 == s1: return [], 0 283 | while s0 < s1: 284 | def th_fn_inner(s): 285 | if s > s1: s = s1 286 | return curve.th(s0 + s, s == 0) 287 | z0 = curve.xy(s0) 288 | z1 = curve.xy(s1) 289 | bz, score = fit_cubic(z0, z1, s1 - s0, th_fn_inner, 0) 290 | if score < thresh or nmax != None and len(result) == nmax - 1: 291 | result.append(bz) 292 | break 293 | r = s1 294 | l = s0 + .001 * (s1 - s0) 295 | for i in range(10): 296 | smid = 0.5 * (l + r) 297 | zmid = curve.xy(smid) 298 | bz, score = fit_cubic(z0, zmid, smid - s0, th_fn_inner, 0) 299 | if score > thresh: 300 | r = smid 301 | else: 302 | l = smid 303 | print '% s0=', s0, 'smid=', smid, 'actual score =', score 304 | result.append(bz) 305 | s0 = smid 306 | print '% last actual score=', score 307 | return result, score 308 | 309 | def pcorn_segment_to_bzs_optim(curve, s0, s1, thresh, optim): 310 | result, score = pcorn_segment_to_bzs_optim_inner(curve, s0, s1, thresh) 311 | bresult, bscore = result, score 312 | if len(result) > 1 and optim > 2: 313 | nmax = len(result) 314 | gamma = 1./6 315 | l = score 316 | r = thresh 317 | for i in range(5): 318 | tmid = (0.5 * (l ** gamma + r ** gamma)) ** (1/gamma) 319 | result, score = pcorn_segment_to_bzs_optim_inner(curve, s0, s1, tmid, nmax) 320 | if score < tmid: 321 | l = max(l, score) 322 | r = tmid 323 | else: 324 | l = tmid 325 | r = min(r, score) 326 | if max(score, tmid) < bscore: 327 | bresult, bscore = result, max(score, tmid) 328 | return result 329 | 330 | def pcorn_segment_to_bzs(curve, s0, s1, optim = 0, thresh = 1e-3): 331 | if optim >= 2: 332 | return pcorn_segment_to_bzs_optim(curve, s0, s1, thresh, optim) 333 | z0 = curve.xy(s0) 334 | z1 = curve.xy(s1) 335 | fast = (optim == 0) 336 | def th_fn(s): 337 | return curve.th(s0 + s, s == 0) 338 | bz, score = fit_cubic(z0, z1, s1 - s0, th_fn, fast) 339 | if score < thresh: 340 | return [bz] 341 | else: 342 | smid = 0.5 * (s0 + s1) 343 | result = pcorn_segment_to_bzs(curve, s0, smid, optim, thresh) 344 | result.extend(pcorn_segment_to_bzs(curve, smid, s1, optim, thresh)) 345 | return result 346 | 347 | def pcorn_curve_to_bzs(curve, optim = 3, thresh = 1e-3): 348 | result = [] 349 | extrema = curve.find_extrema() 350 | extrema.extend(curve.find_breaks()) 351 | extrema.sort() 352 | print '%', extrema 353 | for i in range(len(extrema)): 354 | s0 = extrema[i] 355 | if i == len(extrema) - 1: 356 | s1 = extrema[0] + curve.arclen 357 | else: 358 | s1 = extrema[i + 1] 359 | result.extend(pcorn_segment_to_bzs(curve, s0, s1, optim, thresh)) 360 | return result 361 | 362 | import struct 363 | 364 | def fit_cubic_arclen_forplot(z0, z1, arclen, th0, th1, aab): 365 | chord = hypot(z1[0] - z0[0], z1[1] - z0[1]) 366 | cth0, sth0 = cos(th0), sin(th0) 367 | cth1, sth1 = -cos(th1), -sin(th1) 368 | armlen = .66667 * chord 369 | darmlen = 1e-6 * armlen 370 | for i in range(10): 371 | a = armlen * aab 372 | b = armlen - a 373 | bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), 374 | (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] 375 | actual_s = bz_arclength_rk4(bz) 376 | if (abs(arclen - actual_s) < 1e-12): 377 | break 378 | a = (armlen + darmlen) * aab 379 | b = (armlen + darmlen) - a 380 | bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), 381 | (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] 382 | actual_s2 = bz_arclength_rk4(bz) 383 | ds = (actual_s2 - actual_s) / darmlen 384 | #print '% armlen = ', armlen 385 | armlen += (arclen - actual_s) / ds 386 | a = armlen * aab 387 | b = armlen - a 388 | bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), 389 | (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] 390 | return bz, a, b 391 | 392 | def plot_errors_2d(t0, t1, as_ppm): 393 | xs = 1024 394 | ys = 1024 395 | if as_ppm: 396 | print 'P6' 397 | print xs, ys 398 | print 255 399 | def th_fn(s): 400 | return (s + t0) ** 2 401 | y0, x0 = cornu.eval_cornu(t0) 402 | y1, x1 = cornu.eval_cornu(t1) 403 | z0 = (x0, y0) 404 | z1 = (x1, y1) 405 | chord = hypot(y1 - y0, x1 - x0) 406 | arclen = t1 - t0 407 | th0 = th_fn(0) 408 | th1 = th_fn(arclen) 409 | cth0, sth0 = cos(th0), sin(th0) 410 | cth1, sth1 = -cos(th1), -sin(th1) 411 | 412 | for y in range(ys): 413 | b = .8 * chord * (ys - y - 1) / ys 414 | for x in range(xs): 415 | a = .8 * chord * x / xs 416 | bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), 417 | (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] 418 | s_bz = bz_arclength(bz, 10) 419 | def th_fn_scaled(s): 420 | return (s * arclen / s_bz + t0) ** 2 421 | score = measure_bz_rk4(bz, arclen, th_fn_scaled, 10) 422 | if as_ppm: 423 | ls = -log(score) 424 | color_th = ls 425 | darkstep = 0 426 | if s_bz > arclen: 427 | g0 = 128 - darkstep 428 | else: 429 | g0 = 128 + darkstep 430 | sc = 127 - darkstep 431 | rr = g0 + sc * cos(color_th) 432 | gg = g0 + sc * cos(color_th + 2 * pi / 3) 433 | bb = g0 + sc * cos(color_th - 2 * pi / 3) 434 | sys.stdout.write(struct.pack('3B', rr, gg, bb)) 435 | else: 436 | print a, b, score 437 | if not as_ppm: 438 | print 439 | 440 | def plot_arclen(t0, t1): 441 | def th_fn(s): 442 | return (s + t0) ** 2 443 | y0, x0 = cornu.eval_cornu(t0) 444 | y1, x1 = cornu.eval_cornu(t1) 445 | z0 = (x0, y0) 446 | z1 = (x1, y1) 447 | chord = hypot(y1 - y0, x1 - x0) 448 | arclen = t1 - t0 449 | th0 = th_fn(0) 450 | th1 = th_fn(arclen) 451 | for i in range(101): 452 | aab = i * .01 453 | bz, a, b = fit_cubic_arclen_forplot(z0, z1, arclen, th0, th1, aab) 454 | print a, b 455 | 456 | if __name__ == '__main__': 457 | #test_bz_arclength() 458 | test_draw_cornu() 459 | #run_one_cornu_seg() 460 | #plot_errors_2d(.5, 1.0, False) 461 | #plot_arclen(.5, 1.0) 462 | -------------------------------------------------------------------------------- /font/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -Wall -O2 -g 2 | LDLIBS = -lm 3 | all: blend segment 4 | -------------------------------------------------------------------------------- /font/blend.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef struct { 7 | const char *fn; 8 | int xs; 9 | int ys; 10 | unsigned char *buf; 11 | } pgm; 12 | 13 | typedef struct { 14 | int xs; 15 | int ys; 16 | int *sum; 17 | int *count; 18 | } blendbuf; 19 | 20 | static void 21 | die (char *why) 22 | { 23 | fprintf (stderr, "%s\n", why); 24 | exit (1); 25 | } 26 | 27 | #define MAX_SIZE 65536 28 | 29 | pgm *load_pgm(const char *fn) 30 | { 31 | FILE *fi = fopen(fn, "rb"); 32 | pgm *result; 33 | char buf[256]; 34 | int xs, ys; 35 | int depth; 36 | 37 | if (fi == NULL) 38 | return NULL; 39 | 40 | fgets (buf, sizeof(buf), fi); 41 | if (buf[0] != 'P' || buf[1] != '5') 42 | die ("Need pgmraw image on input"); 43 | 44 | xs = ys = 0; 45 | do 46 | fgets (buf, sizeof(buf), fi); 47 | while (buf[0] == '#'); 48 | sscanf (buf, "%d %d", &xs, &ys); 49 | if (xs <= 0 || ys <= 0 || xs > MAX_SIZE || ys > MAX_SIZE) 50 | die ("Input image size out of range"); 51 | 52 | do 53 | fgets (buf, sizeof(buf), fi); 54 | while (buf[0] == '#'); 55 | sscanf (buf, "%d", &depth); 56 | if (depth != 255) 57 | die ("Only works with depth=255 images"); 58 | 59 | result = (pgm *)malloc(sizeof(pgm)); 60 | 61 | result->fn = fn; 62 | result->xs = xs; 63 | result->ys = ys; 64 | result->buf = (unsigned char *)malloc(xs * ys); 65 | 66 | fread(result->buf, 1, xs * ys, fi); 67 | fprintf(stderr, "loaded file %s %dx%d\n", fn, xs, ys); 68 | fclose(fi); 69 | return result; 70 | } 71 | 72 | int 73 | align_pgms(const pgm *p1, const pgm *p2, int *px, int *py) 74 | { 75 | int xo, yo; 76 | int xa, ya; 77 | int best = 0x7fffffff; 78 | 79 | xa = (p1->xs < p2->xs ? p1->xs : p2->xs) - 20; 80 | ya = (p1->ys < p2->ys ? p1->ys : p2->ys) - 20; 81 | 82 | for (yo = -10; yo <= 10; yo++) 83 | for (xo = -10; xo <= 10; xo++) { 84 | int sum = 0; 85 | int i, j; 86 | 87 | for (j = 0; j < ya; j++) 88 | for (i = 0; i < xa; i++) { 89 | int g1 = p1->buf[(j + 10) * p1->xs + i + 10]; 90 | int g2 = p2->buf[(j + 10 - yo) * p2->xs + i - xo + 10]; 91 | sum += (g1 - g2) * (g1 - g2); 92 | } 93 | if (sum < best) { 94 | best = sum; 95 | *px = xo; 96 | *py = yo; 97 | } 98 | } 99 | return best; 100 | } 101 | 102 | blendbuf * 103 | new_blendbuf(int xs, int ys) 104 | { 105 | blendbuf *result = (blendbuf *)malloc(sizeof(blendbuf)); 106 | int i; 107 | 108 | result->xs = xs; 109 | result->ys = ys; 110 | result->sum = (int *)malloc(sizeof(int) * xs * ys); 111 | result->count = (int *)malloc(sizeof(int) * xs * ys); 112 | for (i = 0; i < xs * ys; i++) { 113 | result->sum[i] = 0; 114 | result->count[i] = 0; 115 | } 116 | 117 | return result; 118 | } 119 | 120 | void 121 | add_pgm(blendbuf *bb, pgm *p, int xo, int yo) 122 | { 123 | int i, j; 124 | 125 | for (j = 0; j < p->ys; j++) { 126 | if (j + yo >= 0 && j + yo < bb->ys) { 127 | for (i = 0; i < p->xs; i++) { 128 | if (i + xo >= 0 && i + xo < bb->xs) { 129 | int ix = (j + yo) * bb->xs + i + xo; 130 | bb->sum[ix] += p->buf[j * p->xs + i]; 131 | bb->count[ix]++; 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | pgm * 139 | pgm_from_blendbuf(blendbuf *bb) 140 | { 141 | int xs = bb->xs; 142 | int ys = bb->ys; 143 | pgm *result = (pgm *)malloc(sizeof(pgm)); 144 | int i, j; 145 | 146 | result->xs = xs; 147 | result->ys = ys; 148 | result->buf = (unsigned char *)malloc(xs * ys); 149 | 150 | for (j = 0; j < ys; j++) { 151 | for (i = 0; i < xs; i++) { 152 | int ix = j * xs + i; 153 | unsigned char g; 154 | if (bb->count[ix]) 155 | g = (bb->sum[ix] + (bb->count[ix] >> 1)) / bb->count[ix]; 156 | else 157 | g = 255; 158 | result->buf[ix] = g; 159 | } 160 | } 161 | return result; 162 | } 163 | 164 | pgm * 165 | pgm_from_blendbuf_enhanced(blendbuf *bb, double sharp, double gamma) 166 | { 167 | int xs = bb->xs; 168 | int ys = bb->ys; 169 | pgm *result = (pgm *)malloc(sizeof(pgm)); 170 | int i, j; 171 | double ming = 255, maxg = 0; 172 | double *tmpbuf; 173 | 174 | result->xs = xs; 175 | result->ys = ys; 176 | result->buf = (unsigned char *)malloc(xs * ys); 177 | 178 | tmpbuf = (double *)malloc(xs * ys * sizeof(double)); 179 | 180 | for (j = 0; j < ys; j++) { 181 | for (i = 0; i < xs; i++) { 182 | int ix = j * xs + i; 183 | double g; 184 | if (bb->count[ix]) { 185 | g = ((double)bb->sum[ix]) / bb->count[ix]; 186 | if (g < ming) ming = g; 187 | if (g > maxg) maxg = g; 188 | } else 189 | g = 255.0; 190 | tmpbuf[ix] = g; 191 | } 192 | } 193 | for (j = 0; j < ys; j++) { 194 | for (i = 0; i < xs; i++) { 195 | int ix = j * xs + i; 196 | double g; 197 | if (bb->count[ix]) { 198 | int u, v; 199 | int cnt = 0; 200 | double sum = 0; 201 | 202 | for (v = -1; v <= 1; v++) { 203 | for (u = -1; u <= 1; u++) { 204 | if (i + u >= 0 && i + u < xs && 205 | j + v >= 0 && j + v < ys && 206 | bb->count[(j + v) * xs + i + u]) { 207 | sum += tmpbuf[(j + v) * xs + i + u]; 208 | cnt += 1; 209 | } 210 | } 211 | } 212 | 213 | g = (1 + sharp) * tmpbuf[ix] - sharp * sum / cnt; 214 | g = (g - ming) / (maxg - ming); 215 | if (g < 0) g = 0; 216 | if (g > 1) g = 1; 217 | g = pow(g, gamma); 218 | } else 219 | g = 1; 220 | result->buf[ix] = (int)(255 * g + 0.5); 221 | } 222 | } 223 | free(tmpbuf); 224 | return result; 225 | } 226 | 227 | void 228 | print_pgm(pgm *p, FILE *f) 229 | { 230 | fprintf(f, "P5\n%d %d\n255\n", p->xs, p->ys); 231 | fwrite(p->buf, 1, p->xs * p->ys, f); 232 | } 233 | 234 | void 235 | threshold(pgm *p) 236 | { 237 | int i; 238 | 239 | for (i = 0; i < p->xs * p->ys; i++) 240 | p->buf[i] = p->buf[i] > 128 ? 1 : 0; 241 | } 242 | 243 | int 244 | classify(pgm **pgmlist, int n_pgm) 245 | { 246 | int *class = (int *)malloc(sizeof(int) * n_pgm); 247 | int n_class = 0; 248 | int i, j; 249 | int tshift = 4; 250 | 251 | for (i = 0; i < n_pgm; i++) 252 | class[i] = -1; 253 | 254 | for (i = 0; i < n_pgm; i++) { 255 | pgm *pi = pgmlist[i]; 256 | 257 | if (class[i] == -1) { 258 | class[i] = n_class++; 259 | for (j = i + 1; j < n_pgm; j++) { 260 | pgm *pj = pgmlist[j]; 261 | int xo, yo; 262 | int score; 263 | 264 | if (abs(pi->xs - pj->xs) < 10 && 265 | abs(pi->ys - pj->ys) < 10) { 266 | score = align_pgms(pi, pj, &xo, &yo); 267 | if (score < ((pi->xs - 20) * (pi->ys - 20)) >> tshift) { 268 | class[j] = class[i]; 269 | } 270 | } 271 | } 272 | } 273 | printf("%s: class%d\n", pi->fn, class[i]); 274 | fflush(stdout); 275 | } 276 | free(class); 277 | return 0; 278 | } 279 | 280 | 281 | static int 282 | intcompar(const void *a, const void *b) { 283 | return *((int *)a) - *((int *)b); 284 | } 285 | 286 | int 287 | do_blend(pgm **pgmlist, int n_pgm, int n_passes, float thresh) 288 | { 289 | blendbuf *bb; 290 | int pass; 291 | int i; 292 | pgm *base = pgmlist[0]; 293 | int *scores, *scores2, *xos, *yos; 294 | 295 | scores = (int *)malloc(n_pgm * sizeof(int)); 296 | scores2 = (int *)malloc(n_pgm * sizeof(int)); 297 | xos = (int *)malloc(n_pgm * sizeof(int)); 298 | yos = (int *)malloc(n_pgm * sizeof(int)); 299 | 300 | for (pass = 0; pass < n_passes; pass++) { 301 | int scorethresh; 302 | 303 | bb = new_blendbuf(base->xs, base->ys); 304 | for (i = 0; i < n_pgm; i++) { 305 | int xo, yo; 306 | int score = align_pgms(base, pgmlist[i], &xo, &yo); 307 | fprintf(stderr, "%s: score = %d, offset = %d, %d\n", 308 | pgmlist[i]->fn, score, xo, yo); 309 | scores[i] = score; 310 | xos[i] = xo; 311 | yos[i] = yo; 312 | } 313 | 314 | if (pass == 0) { 315 | scorethresh = 0x7fffffff; 316 | } else { 317 | memcpy(scores2, scores, n_pgm * sizeof(int)); 318 | qsort(scores2, n_pgm, sizeof(int), intcompar); 319 | scorethresh = scores2[(int)(thresh * n_pgm)]; 320 | } 321 | 322 | for (i = 0; i < n_pgm; i++) { 323 | if (pass > 0) 324 | fprintf(stderr, "%s: score = %d %s\n", 325 | pgmlist[i]->fn, scores[i], 326 | scores[i] <= scorethresh ? "ok" : "-"); 327 | if (scores[i] <= scorethresh) 328 | add_pgm(bb, pgmlist[i], xos[i], yos[i]); 329 | } 330 | 331 | if (pass == n_passes - 1) 332 | base = pgm_from_blendbuf_enhanced(bb, 5, 0.5); 333 | else 334 | base = pgm_from_blendbuf(bb); 335 | if (pass != n_passes - 1) 336 | fprintf(stderr, "\n"); 337 | } 338 | 339 | free(scores); 340 | free(xos); 341 | free(yos); 342 | print_pgm(base, stdout); 343 | return 0; 344 | } 345 | 346 | int 347 | main(int argc, char **argv) 348 | { 349 | int i; 350 | int n_pgm = 0; 351 | pgm **pgmlist = (pgm **)malloc(sizeof(pgm *) * argc - 1); 352 | int do_class = 0; 353 | int n_pass = 2; 354 | float thresh = 0.90; 355 | 356 | for (i = 1; i < argc; i++) { 357 | if (!strcmp(argv[i], "-c")) 358 | do_class = 1; 359 | else 360 | pgmlist[n_pgm++] = load_pgm(argv[i]); 361 | } 362 | 363 | if (do_class) { 364 | for (i = 0; i < n_pgm; i++) 365 | threshold(pgmlist[i]); 366 | return classify(pgmlist, n_pgm); 367 | } else { 368 | return do_blend(pgmlist, n_pgm, n_pass, thresh); 369 | } 370 | 371 | return 0; 372 | } 373 | -------------------------------------------------------------------------------- /font/cut.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | athresh = 100 4 | border = 20 5 | 6 | segf = sys.argv[1] 7 | if len(sys.argv) > 2: 8 | pref = sys.argv[2] 9 | else: 10 | pref = '/tmp/cut' 11 | rects = [] 12 | starts = {} 13 | for l in file(segf).xreadlines(): 14 | ls = l.split() 15 | if len(ls) == 6 and ls[-1] == 'rect': 16 | r = map(int, ls[:4]) 17 | area = (r[2] - r[0]) * (r[3] - r[1]) 18 | if area > athresh: 19 | rpad = [r[0] - border, r[1] - border, r[2] + border, r[3] + border] 20 | if not starts.has_key(rpad[1]): 21 | starts[rpad[1]] = [] 22 | starts[rpad[1]].append(len(rects)) 23 | rects.append(rpad) 24 | inf = sys.stdin 25 | l = inf.readline() 26 | if l != 'P5\n': 27 | raise 'expected pgm file' 28 | while 1: 29 | l = inf.readline() 30 | if l[0] != '#': break 31 | x, y = map(int, l.split()) 32 | l = inf.readline() 33 | 34 | active = {} 35 | for j in range(y): 36 | if starts.has_key(j): 37 | for ix in starts[j]: 38 | r = rects[ix] 39 | ofn = pref + '%04d.pgm' % ix 40 | of = file(ofn, 'w') 41 | active[ix] = of 42 | print >> of, 'P5' 43 | print >> of, r[2] - r[0], r[3] - r[1] 44 | print >> of, '255' 45 | buf = inf.read(x) 46 | for ix, of in active.items(): 47 | r = rects[ix] 48 | of.write(buf[r[0]:r[2]]) 49 | if j == r[3] - 1: 50 | of.close() 51 | del active[ix] 52 | 53 | -------------------------------------------------------------------------------- /font/mkblends.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | 3 | glyphmap = {} 4 | for ln in file(sys.argv[1]).xreadlines(): 5 | fnglyph = ln.strip().split(': ') 6 | if len(fnglyph) == 2: 7 | fn, name = fnglyph 8 | pgmf = fn[:-4] + '.pgm' 9 | if not glyphmap.has_key(name): 10 | glyphmap[name] = [] 11 | glyphmap[name].append(pgmf) 12 | for name in glyphmap.iterkeys(): 13 | cmd = '~/garden/font/blend ' + ' '.join(glyphmap[name]) + ' | pnmtopng > ' + name + '.png' 14 | print cmd 15 | os.system(cmd) 16 | 17 | -------------------------------------------------------------------------------- /font/replace_class.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | srfile = file(sys.argv[1]) 5 | table = {} 6 | for line in srfile.xreadlines(): 7 | clas, repl = line.split() 8 | if repl[-1] not in '-?': 9 | table['class' + clas] = repl 10 | 11 | for line in sys.stdin.xreadlines(): 12 | fn, clas = line.split() 13 | if table.has_key(clas): 14 | print fn, table[clas] 15 | -------------------------------------------------------------------------------- /font/segment.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define MIN(a,b) ((a) < (b) ? (a) : (b)) 6 | #define MAX(a,b) ((a) > (b) ? (a) : (b)) 7 | 8 | typedef struct { 9 | int x0; 10 | int x1; 11 | int y0; 12 | int y1; 13 | double xmom; 14 | double ymom; 15 | int area; 16 | } seg; 17 | 18 | typedef struct { 19 | int x0; 20 | int x1; 21 | } run; 22 | 23 | void 24 | find_runs(run *result, const unsigned char *buf, unsigned char thresh, int xs) 25 | { 26 | int x; 27 | int j = 0; 28 | 29 | for (x = 0; x < xs;) { 30 | int x0, x1; 31 | for (x0 = x; x0 < xs; x0++) 32 | if (buf[x0] < thresh) 33 | break; 34 | if (x0 == xs) 35 | break; 36 | for (x1 = x0 + 1; x1 < xs; x1++) 37 | if (buf[x1] >= thresh) 38 | break; 39 | result[j].x0 = x0; 40 | result[j].x1 = x1; 41 | j++; 42 | x = x1 + 1; 43 | } 44 | result[j].x0 = 0; 45 | result[j].x1 = 0; 46 | } 47 | 48 | void 49 | print_rect(const seg *r) 50 | { 51 | printf("%d %d %d %d %g rect\n", r->x0, r->y0, r->x1, r->y1, 52 | r->area / (1.0 * (r->x1 - r->x0) * (r->y1 - r->y0))); 53 | printf("%g %g ci\n", r->xmom / r->area, r->ymom / r->area); 54 | } 55 | 56 | void 57 | merge_runs(seg *buf, const run *new_runs, int y) 58 | { 59 | int bi, ni; 60 | int bs; 61 | int bi0; 62 | int flag; 63 | 64 | for (bs = 0; buf[bs].x1; bs++); 65 | 66 | bi = 0; 67 | flag = 1; 68 | for (ni = 0; new_runs[ni].x1; ni++) { 69 | int run_len = new_runs[ni].x1 - new_runs[ni].x0; 70 | 71 | bi0 = bi; 72 | for (; bi < bs && buf[bi].x1 <= new_runs[ni].x0; bi++) { 73 | if (flag) { 74 | buf[bi].y1 = y; 75 | print_rect(&buf[bi]); 76 | } else { 77 | bi0 = bi + 1; 78 | flag = 1; 79 | } 80 | } 81 | if (bi > bi0) { 82 | memmove(&buf[bi0], &buf[bi], (bs - bi) * sizeof(seg)); 83 | bs += bi0 - bi; 84 | } 85 | bi = bi0; 86 | if (bi < bs && buf[bi].x0 < new_runs[ni].x1) { 87 | double xmom = buf[bi].xmom; 88 | double ymom = buf[bi].ymom; 89 | int area = buf[bi].area; 90 | int y0 = buf[bi].y0; 91 | 92 | for (bi = bi + 1; bi < bs && buf[bi].x0 < new_runs[ni].x1; bi++) { 93 | y0 = MIN(y0, buf[bi].y0); 94 | xmom += buf[bi].xmom; 95 | ymom += buf[bi].ymom; 96 | area += buf[bi].area; 97 | } 98 | buf[bi0].x0 = MIN(buf[bi0].x0, new_runs[ni].x0); 99 | buf[bi0].x1 = MAX(buf[bi - 1].x1, new_runs[ni].x1); 100 | buf[bi0].y0 = y0; 101 | buf[bi0].xmom = xmom + run_len * .5 * (new_runs[ni].x0 + new_runs[ni].x1); 102 | buf[bi0].ymom = ymom + run_len * y; 103 | buf[bi0].area = area + run_len; 104 | if (bi > bi0 + 1) { 105 | memmove(&buf[bi0 + 1], &buf[bi], (bs - bi) * sizeof(seg)); 106 | bs += bi0 + 1 - bi; 107 | } 108 | bi = bi0; 109 | flag = 0; 110 | } else { 111 | memmove(&buf[bi + 1], &buf[bi], (bs - bi) * sizeof(seg)); 112 | bs++; 113 | buf[bi].x0 = new_runs[ni].x0; 114 | buf[bi].x1 = new_runs[ni].x1; 115 | buf[bi].y0 = y; 116 | buf[bi].xmom = run_len * .5 * (new_runs[ni].x0 + new_runs[ni].x1); 117 | buf[bi].ymom = run_len * y; 118 | buf[bi].area = run_len; 119 | bi++; 120 | flag = 1; 121 | } 122 | } 123 | bi0 = bi; 124 | for (; bi < bs; bi++) { 125 | if (flag) { 126 | buf[bi].y1 = y; 127 | print_rect(&buf[bi]); 128 | } else { 129 | bi0 = bi + 1; 130 | flag = 1; 131 | } 132 | } 133 | buf[bi0].x0 = 0; 134 | buf[bi0].x1 = 0; 135 | } 136 | 137 | static void 138 | die (char *why) 139 | { 140 | fprintf (stderr, "%s\n", why); 141 | exit (1); 142 | } 143 | 144 | #define MAX_SIZE 65536 145 | 146 | int 147 | main (int argc, char **argv) 148 | { 149 | FILE *fi = stdin; 150 | FILE *fo = stdout; 151 | char buf[256]; 152 | int xs, ys; 153 | int depth; 154 | unsigned char *imgbuf; 155 | seg *segs; 156 | run *runs; 157 | int y; 158 | 159 | fgets (buf, sizeof(buf), fi); 160 | if (buf[0] != 'P' || buf[1] != '5') 161 | die ("Need pgmraw image on input"); 162 | 163 | xs = ys = 0; 164 | do 165 | fgets (buf, sizeof(buf), fi); 166 | while (buf[0] == '#'); 167 | sscanf (buf, "%d %d", &xs, &ys); 168 | if (xs <= 0 || ys <= 0 || xs > MAX_SIZE || ys > MAX_SIZE) 169 | die ("Input image size out of range"); 170 | 171 | do 172 | fgets (buf, sizeof(buf), fi); 173 | while (buf[0] == '#'); 174 | sscanf (buf, "%d", &depth); 175 | if (depth != 255) 176 | die ("Only works with depth=255 images"); 177 | 178 | runs = (run *)malloc(((xs + 3) >> 1) * sizeof(run)); 179 | segs = (seg *)malloc(((xs + 3) >> 1) * sizeof(seg)); 180 | imgbuf = (unsigned char *)malloc (xs); 181 | segs[0].x0 = 0; 182 | segs[0].x1 = 0; 183 | for (y = 0; y < ys; y++) { 184 | fread (imgbuf, 1, xs, fi); 185 | find_runs(runs, imgbuf, 160, xs); 186 | merge_runs(segs, runs, y); 187 | } 188 | 189 | free(imgbuf); 190 | free(runs); 191 | free(segs); 192 | 193 | return 0; 194 | } 195 | -------------------------------------------------------------------------------- /ppedit/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = gtk 2 | 3 | ifeq ($(TARGET),gtk) 4 | X3_PLAT = X3_GTK 5 | X3_INCL = `pkg-config --cflags gtk+-2.0` 6 | X3_LIBS = `pkg-config --libs gtk+-2.0` 7 | endif 8 | 9 | ifeq ($(TARGET),carbon) 10 | X3_PLAT = X3_CARBON 11 | X3_LIBS = -framework Carbon 12 | endif 13 | 14 | ifeq ($(TARGET),win32) 15 | X3_PLAT = X3_WIN32 16 | X3_LIBS = -lgdi32 17 | endif 18 | 19 | CFLAGS = -g -Wall -D$(X3_PLAT) $(X3_INCL) -I../x3/ 20 | LDFLAGS = -g 21 | LDLIBS = $(X3_LIBS) 22 | 23 | all: ppedit 24 | 25 | 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 26 | -------------------------------------------------------------------------------- /ppedit/Makefile_gtk1: -------------------------------------------------------------------------------- 1 | CFLAGS = -Wall -g `gtk-config --cflags` `libart2-config --cflags` 2 | LDLIBS = `gtk-config --libs` `libart2-config --libs` 3 | all: ppedit_gtk1 4 | 5 | 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 6 | -------------------------------------------------------------------------------- /ppedit/README: -------------------------------------------------------------------------------- 1 | README for ppedit 2 | 3 | Raph Levien 4 | 4 May 2007 5 | 6 | ppedit is my prototype application for editing curves using my 7 | curvature-continuous spirals. While I have used this code to draw many 8 | font outlines, it is very rough around the edges, and is far from a 9 | polished tool. 10 | 11 | 12 | == License and patent grant == 13 | 14 | Update as of 10 May 2019 (the previous license was GNU General Public License 15 | with a patent grant): 16 | 17 | The code in this repository is licensed under the terms of the [Apache-2](../LICENSE-APACHE) or 18 | [MIT](../LICENSE-MIT) license, at your choice. 19 | 20 | The ideas in this repository are free for all to implement. Previously there 21 | were patents and a provisional patent application, but those are hereby passed 22 | into the public domain. 23 | 24 | == Building == 25 | 26 | The main build supported right now is the Gtk2/cairo one. There's also 27 | a Mac build and a Gtk1 one, but those aren't guaranteed to work. 28 | 29 | 1. Make sure you've got ../x3/ in a directory parallel to ppedit. If 30 | you've unpacked from a tarball, this should be the case already. 31 | From darcs, use: darcs get http://levien.com/garden/x3 32 | 33 | 2. make 34 | 35 | 3. The binary is ppedit 36 | 37 | == Using == 38 | 39 | The numeric keys 1-6 select the mode. 1 is selection, 2-6 select 40 | different point modes: 41 | 42 | 2: Add G4-continuous curve point 43 | 3: Add corner point 44 | 4: Add left-facing one-way point 45 | 5: Add right-facing one-way point 46 | 6: Add G2-continuous curve point 47 | 48 | Note: Dave Crossland has a set of alternate keybindings which are 49 | probably faster. 50 | 51 | == Plate files == 52 | 53 | Ctrl-S saves a plate file in a file of the name 'plate'. Additionally, 54 | a plate file can be given as a command line argument. The file uses 55 | simple S-expressions, with a one-character code for each point, then 56 | the X and Y coordinates - 0,0 is top left. 57 | 58 | Here's the cap U from Inconsolata, for example: 59 | 60 | (plate 61 | (v 68 78) 62 | (v 159 78) 63 | (o 158 92) 64 | ([ 148 115) 65 | (] 148 552) 66 | (o 298 744) 67 | ([ 459 549) 68 | (v 459 78) 69 | (v 536 78) 70 | (] 536 547) 71 | (o 295 813) 72 | ([ 68 551) 73 | (z) 74 | ) 75 | 76 | v: corner 77 | o: g4 78 | c: g2 79 | [: left-facing one-way 80 | ]: right-facing one-way 81 | 82 | == Conversion to PostScript == 83 | 84 | Ctrl-P converts to PostScript, saving '/tmp/foo.ps'. Other utilities 85 | can convert that representation into FontForge, and also optimize the 86 | Beziers. 87 | 88 | == Stability == 89 | 90 | The spline solver in this release is _not_ numerically robust. When 91 | you start drawing random points, you'll quickly run into divergence. 92 | However, "sensible" plates based on real fonts usually converge. Some 93 | tips: 94 | 95 | 1. Huge changes of angle are likely to diverge. 96 | 97 | 2. For the first two or three points, G4 points are likelier to 98 | converge than G2's. For longer segments, G2 is more likely. 99 | 100 | 3. Start on a curve point. 101 | 102 | A more numerically robust approach is in the works. 103 | -------------------------------------------------------------------------------- /ppedit/bezctx.c: -------------------------------------------------------------------------------- 1 | /* 2 | ppedit - A pattern plate editor for Spiro splines. 3 | Copyright (C) 2007 Raph Levien 4 | 5 | Licensed under the Apache License, Version 2.0 or the MIT license 7 | , at your 8 | option. This file may not be copied, modified, or distributed 9 | except according to those terms. 10 | 11 | */ 12 | #include "bezctx.h" 13 | 14 | void bezctx_moveto(bezctx *bc, double x, double y, int is_open) 15 | { 16 | bc->moveto(bc, x, y, is_open); 17 | } 18 | 19 | void bezctx_lineto(bezctx *bc, double x, double y) 20 | { 21 | bc->lineto(bc, x, y); 22 | } 23 | 24 | void bezctx_quadto(bezctx *bc, double x1, double y1, double x2, double y2) 25 | { 26 | bc->quadto(bc, x1, y1, x2, y2); 27 | } 28 | 29 | void bezctx_curveto(bezctx *bc, double x1, double y1, double x2, double y2, 30 | double x3, double y3) 31 | { 32 | bc->curveto(bc, x1, y1, x2, y2, x3, y3); 33 | } 34 | 35 | void bezctx_mark_knot(bezctx *bc, int knot_idx) 36 | { 37 | if (bc->mark_knot) 38 | bc->mark_knot(bc, knot_idx); 39 | } 40 | -------------------------------------------------------------------------------- /ppedit/bezctx.h: -------------------------------------------------------------------------------- 1 | #include "bezctx_intf.h" 2 | 3 | struct _bezctx { 4 | void (*moveto)(bezctx *bc, double x, double y, int is_open); 5 | void (*lineto)(bezctx *bc, double x, double y); 6 | void (*quadto)(bezctx *bc, double x1, double y1, double x2, double y2); 7 | void (*curveto)(bezctx *bc, double x1, double y1, double x2, double y2, 8 | double x3, double y3); 9 | void (*mark_knot)(bezctx *bc, int knot_idx); 10 | }; 11 | -------------------------------------------------------------------------------- /ppedit/bezctx_hittest.c: -------------------------------------------------------------------------------- 1 | /* 2 | ppedit - A pattern plate editor for Spiro splines. 3 | Copyright (C) 2007 Raph Levien 4 | 5 | Licensed under the Apache License, Version 2.0 or the MIT license 7 | , at your 8 | option. This file may not be copied, modified, or distributed 9 | except according to those terms. 10 | 11 | */ 12 | #include "zmisc.h" 13 | #include "bezctx.h" 14 | #include "bezctx_hittest.h" 15 | #include 16 | 17 | typedef struct { 18 | bezctx base; 19 | double x, y; 20 | 21 | double x0, y0; 22 | int knot_idx; 23 | 24 | int knot_idx_min; 25 | double r_min; 26 | } bezctx_hittest; 27 | 28 | static void 29 | bezctx_hittest_moveto(bezctx *z, double x, double y, int is_open) { 30 | bezctx_hittest *bc = (bezctx_hittest *)z; 31 | 32 | bc->x0 = x; 33 | bc->y0 = y; 34 | } 35 | 36 | static void 37 | bezctx_hittest_lineto(bezctx *z, double x, double y) { 38 | bezctx_hittest *bc = (bezctx_hittest *)z; 39 | double x0 = bc->x0; 40 | double y0 = bc->y0; 41 | double dx = x - x0; 42 | double dy = y - y0; 43 | double dotp = (bc->x - x0) * dx + (bc->y - y0) * dy; 44 | double lin_dotp = dx * dx + dy * dy; 45 | double r_min, r; 46 | 47 | r = hypot(bc->x - x0, bc->y - y0); 48 | r_min = r; 49 | r = hypot(bc->x - x, bc->y - y); 50 | if (r < r_min) r_min = r; 51 | 52 | if (dotp >= 0 && dotp <= lin_dotp) { 53 | double norm = (bc->x - x0) * dy - (bc->y - y0) * dx; 54 | r = fabs(norm / sqrt(lin_dotp)); 55 | if (r < r_min) r_min = r; 56 | } 57 | 58 | if (r_min < bc->r_min) { 59 | bc->r_min = r_min; 60 | bc->knot_idx_min = bc->knot_idx; 61 | } 62 | 63 | bc->x0 = x; 64 | bc->y0 = y; 65 | } 66 | 67 | #define cube(x) ((x) * (x) * (x)) 68 | 69 | static double 70 | my_cbrt(double x) 71 | { 72 | if (x >= 0) 73 | return pow(x, 1.0 / 3.0); 74 | else 75 | return -pow(-x, 1.0 / 3.0); 76 | } 77 | 78 | /** 79 | * Give real roots to eqn c0 + c1 * x + c2 * x^2 + c3 * x^3 == 0. 80 | * Return value is number of roots found. 81 | **/ 82 | static int 83 | solve_cubic(double c0, double c1, double c2, double c3, double root[3]) 84 | { 85 | double p, q, r, a, b, Q, x0; 86 | 87 | p = c2 / c3; 88 | q = c1 / c3; 89 | r = c0 / c3; 90 | a = (3 * q - p * p) / 3; 91 | b = (2 * cube(p) - 9 * p * q + 27 * r) / 27; 92 | Q = b * b / 4 + cube(a) / 27; 93 | x0 = p / 3; 94 | if (Q > 0) { 95 | double sQ = sqrt(Q); 96 | double t1 = my_cbrt(-b/2 + sQ) + my_cbrt(-b/2 - sQ); 97 | root[0] = t1 - x0; 98 | return 1; 99 | } else if (Q == 0) { 100 | double t1 = my_cbrt(b / 2); 101 | double x1 = t1 - x0; 102 | root[0] = x1; 103 | root[1] = x1; 104 | root[2] = -2 * t1 - x0; 105 | return 3; 106 | } else { 107 | double sQ = sqrt(-Q); 108 | double rho = hypot(-b/2, sQ); 109 | double th = atan2(sQ, -b/2); 110 | double cbrho = my_cbrt(rho); 111 | double c = cos(th / 3); 112 | double s = sin(th / 3); 113 | double sqr3 = sqrt(3); 114 | root[0] = 2 * cbrho * c - x0; 115 | root[1] = -cbrho * (c + sqr3 * s) - x0; 116 | root[2] = -cbrho * (c - sqr3 * s) - x0; 117 | return 3; 118 | } 119 | } 120 | 121 | 122 | static double 123 | dist_to_quadratic(double x, double y, 124 | double x0, double y0, 125 | double x1, double y1, 126 | double x2, double y2) 127 | { 128 | double u0, u1, t0, t1, t2, c0, c1, c2, c3; 129 | double roots[3]; 130 | int n_roots; 131 | double ts[4]; 132 | int n_ts; 133 | int i; 134 | double minerr = 0; 135 | 136 | u0 = x1 - x0; 137 | u1 = x0 - 2 * x1 + x2; 138 | t0 = x0 - x; 139 | t1 = 2 * u0; 140 | t2 = u1; 141 | c0 = t0 * u0; 142 | c1 = t1 * u0 + t0 * u1; 143 | c2 = t2 * u0 + t1 * u1; 144 | c3 = t2 * u1; 145 | 146 | u0 = y1 - y0; 147 | u1 = y0 - 2 * y1 + y2; 148 | t0 = y0 - y; 149 | t1 = 2 * u0; 150 | t2 = u1; 151 | c0 += t0 * u0; 152 | c1 += t1 * u0 + t0 * u1; 153 | c2 += t2 * u0 + t1 * u1; 154 | c3 += t2 * u1; 155 | 156 | n_roots = solve_cubic(c0, c1, c2, c3, roots); 157 | n_ts = 0; 158 | for (i = 0; i < n_roots; i++) { 159 | double t = roots[i]; 160 | if (t > 0 && t < 1) 161 | ts[n_ts++] = t; 162 | } 163 | if (n_ts < n_roots) { 164 | ts[n_ts++] = 0; 165 | ts[n_ts++] = 1; 166 | } 167 | for (i = 0; i < n_ts; i++) { 168 | double t = ts[i]; 169 | double xa = x0 * (1 - t) * (1 - t) + 2 * x1 * (1 - t) * t + x2 * t * t; 170 | double ya = y0 * (1 - t) * (1 - t) + 2 * y1 * (1 - t) * t + y2 * t * t; 171 | double err = hypot(xa - x, ya - y); 172 | if (i == 0 || err < minerr) { 173 | minerr = err; 174 | } 175 | } 176 | return minerr; 177 | } 178 | 179 | static void 180 | bezctx_hittest_quadto(bezctx *z, double x1, double y1, double x2, double y2) 181 | { 182 | bezctx_hittest *bc = (bezctx_hittest *)z; 183 | double r = dist_to_quadratic(bc->x, bc->y, 184 | bc->x0, bc->y0, x1, y1, x2, y2); 185 | 186 | if (r < bc->r_min) { 187 | bc->r_min = r; 188 | bc->knot_idx_min = bc->knot_idx; 189 | } 190 | bc->x0 = x2; 191 | bc->y0 = y2; 192 | } 193 | 194 | static void 195 | bezctx_hittest_curveto(bezctx *z, double x1, double y1, double x2, double y2, 196 | double x3, double y3) 197 | { 198 | bezctx_hittest *bc = (bezctx_hittest *)z; 199 | double x0 = bc->x0; 200 | double y0 = bc->y0; 201 | int n_subdiv = 32; 202 | int i; 203 | double xq2, yq2; 204 | 205 | /* todo: subdivide to quadratics rather than lines */ 206 | for (i = 0; i < n_subdiv; i++) { 207 | double t = (1. / n_subdiv) * (i + 1); 208 | double mt = 1 - t; 209 | 210 | xq2 = x0 * mt * mt * mt + 3 * x1 * mt * t * t + 3 * x2 * mt * mt * t + 211 | x3 * t * t * t; 212 | yq2 = y0 * mt * mt * mt + 3 * y1 * mt * t * t + 3 * y2 * mt * mt * t + 213 | y3 * t * t * t; 214 | bezctx_hittest_lineto(z, xq2, yq2); 215 | } 216 | } 217 | 218 | static void 219 | bezctx_hittest_mark_knot(bezctx *z, int knot_idx) { 220 | bezctx_hittest *bc = (bezctx_hittest *)z; 221 | 222 | bc->knot_idx = knot_idx; 223 | } 224 | 225 | bezctx * 226 | new_bezctx_hittest(double x, double y) { 227 | bezctx_hittest *result = znew(bezctx_hittest, 1); 228 | 229 | result->base.moveto = bezctx_hittest_moveto; 230 | result->base.lineto = bezctx_hittest_lineto; 231 | result->base.quadto = bezctx_hittest_quadto; 232 | result->base.curveto = bezctx_hittest_curveto; 233 | result->base.mark_knot = bezctx_hittest_mark_knot; 234 | result->x = x; 235 | result->y = y; 236 | result->knot_idx_min = -1; 237 | result->r_min = 1e12; 238 | return &result->base; 239 | } 240 | 241 | double 242 | bezctx_hittest_report(bezctx *z, int *p_knot_idx) 243 | { 244 | bezctx_hittest *bc = (bezctx_hittest *)z; 245 | double r_min = bc->r_min; 246 | 247 | if (p_knot_idx) 248 | *p_knot_idx = bc->knot_idx_min; 249 | 250 | zfree(z); 251 | return r_min; 252 | } 253 | -------------------------------------------------------------------------------- /ppedit/bezctx_hittest.h: -------------------------------------------------------------------------------- 1 | bezctx * 2 | new_bezctx_hittest(double x, double y); 3 | 4 | double 5 | bezctx_hittest_report(bezctx *z, int *p_knot_idx); 6 | -------------------------------------------------------------------------------- /ppedit/bezctx_intf.h: -------------------------------------------------------------------------------- 1 | typedef struct _bezctx bezctx; 2 | 3 | bezctx * 4 | new_bezctx(void); 5 | 6 | void 7 | bezctx_moveto(bezctx *bc, double x, double y, int is_open); 8 | 9 | void 10 | bezctx_lineto(bezctx *bc, double x, double y); 11 | 12 | void 13 | bezctx_quadto(bezctx *bc, double x1, double y1, double x2, double y2); 14 | 15 | void 16 | bezctx_curveto(bezctx *bc, double x1, double y1, double x2, double y2, 17 | double x3, double y3); 18 | 19 | void 20 | bezctx_mark_knot(bezctx *bc, int knot_idx); 21 | -------------------------------------------------------------------------------- /ppedit/bezctx_libart.c: -------------------------------------------------------------------------------- 1 | /* 2 | ppedit - A pattern plate editor for Spiro splines. 3 | Copyright (C) 2007 Raph Levien 4 | 5 | Licensed under the Apache License, Version 2.0 or the MIT license 7 | , at your 8 | option. This file may not be copied, modified, or distributed 9 | except according to those terms. 10 | 11 | */ 12 | #include 13 | #include "zmisc.h" 14 | #include "bezctx.h" 15 | #include "bezctx_libart.h" 16 | 17 | typedef struct { 18 | bezctx base; 19 | int n_bez; 20 | int n_bez_max; 21 | ArtBpath *bez; 22 | } bezctx_libart; 23 | 24 | static void 25 | bezctx_libart_moveto(bezctx *z, double x, double y, int is_open) { 26 | bezctx_libart *bc = (bezctx_libart *)z; 27 | ArtBpath *bp; 28 | 29 | if (bc->n_bez == bc->n_bez_max) { 30 | bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); 31 | } 32 | bp = &bc->bez[bc->n_bez++]; 33 | bp->code = is_open ? ART_MOVETO_OPEN : ART_MOVETO; 34 | bp->x3 = x; 35 | bp->y3 = y; 36 | } 37 | 38 | void 39 | bezctx_libart_lineto(bezctx *z, double x, double y) { 40 | bezctx_libart *bc = (bezctx_libart *)z; 41 | ArtBpath *bp; 42 | 43 | if (bc->n_bez == bc->n_bez_max) { 44 | bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); 45 | } 46 | bp = &bc->bez[bc->n_bez++]; 47 | bp->code = ART_LINETO; 48 | bp->x3 = x; 49 | bp->y3 = y; 50 | } 51 | 52 | void 53 | bezctx_libart_quadto(bezctx *z, double x1, double y1, double x2, double y2) 54 | { 55 | bezctx_libart *bc = (bezctx_libart *)z; 56 | ArtBpath *bp; 57 | double x0, y0; 58 | 59 | if (bc->n_bez == bc->n_bez_max) { 60 | bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); 61 | } 62 | bp = &bc->bez[bc->n_bez++]; 63 | x0 = bp[-1].x3; 64 | y0 = bp[-1].y3; 65 | bp->code = ART_CURVETO; 66 | bp->x1 = x1 + (1./3) * (x0 - x1); 67 | bp->y1 = y1 + (1./3) * (y0 - y1); 68 | bp->x2 = x1 + (1./3) * (x2 - x1); 69 | bp->y2 = y1 + (1./3) * (y2 - y1); 70 | bp->x3 = x2; 71 | bp->y3 = y2; 72 | } 73 | 74 | void 75 | bezctx_libart_curveto(bezctx *z, double x1, double y1, double x2, double y2, 76 | double x3, double y3) 77 | { 78 | bezctx_libart *bc = (bezctx_libart *)z; 79 | ArtBpath *bp; 80 | 81 | if (bc->n_bez == bc->n_bez_max) { 82 | bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); 83 | } 84 | bp = &bc->bez[bc->n_bez++]; 85 | bp->code = ART_CURVETO; 86 | bp->x1 = x1; 87 | bp->y1 = y1; 88 | bp->x2 = x2; 89 | bp->y2 = y2; 90 | bp->x3 = x3; 91 | bp->y3 = y3; 92 | } 93 | 94 | ArtBpath * 95 | bezctx_to_bpath(bezctx *z) { 96 | bezctx_libart *bc = (bezctx_libart *)z; 97 | ArtBpath *result; 98 | 99 | if (bc->n_bez == bc->n_bez_max) { 100 | bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); 101 | } 102 | bc->bez[bc->n_bez].code = ART_END; 103 | result = bc->bez; 104 | zfree(bc); 105 | return result; 106 | } 107 | 108 | bezctx * 109 | new_bezctx_libart(void) { 110 | bezctx_libart *result = znew(bezctx_libart, 1); 111 | 112 | result->base.moveto = bezctx_libart_moveto; 113 | result->base.lineto = bezctx_libart_lineto; 114 | result->base.quadto = bezctx_libart_quadto; 115 | result->base.curveto = bezctx_libart_curveto; 116 | result->base.mark_knot = NULL; 117 | result->n_bez = 0; 118 | result->n_bez_max = 4; 119 | result->bez = znew(ArtBpath, result->n_bez_max); 120 | return &result->base; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /ppedit/bezctx_libart.h: -------------------------------------------------------------------------------- 1 | 2 | bezctx *new_bezctx_libart(void); 3 | 4 | ArtBpath * 5 | bezctx_to_bpath(bezctx *bc); 6 | -------------------------------------------------------------------------------- /ppedit/bezctx_ps.c: -------------------------------------------------------------------------------- 1 | /* 2 | ppedit - A pattern plate editor for Spiro splines. 3 | Copyright (C) 2007 Raph Levien 4 | 5 | Licensed under the Apache License, Version 2.0 or the MIT license 7 | , at your 8 | option. This file may not be copied, modified, or distributed 9 | except according to those terms. 10 | 11 | */ 12 | #include 13 | 14 | #include "zmisc.h" 15 | #include "bezctx.h" 16 | #include "bezctx_ps.h" 17 | 18 | typedef struct { 19 | bezctx base; 20 | int is_open; 21 | double x, y; 22 | FILE *f; 23 | } bezctx_ps; 24 | 25 | const char *ps_prolog = "%!PS\n" 26 | "/m { moveto } bind def\n" 27 | "/l { lineto } bind def\n" 28 | "/c { curveto } bind def\n" 29 | "/z { closepath } bind def\n" 30 | "1 -1 scale\n" 31 | "0 -792 translate\n"; 32 | 33 | const char *ps_postlog = "stroke\n" 34 | "showpage\n"; 35 | 36 | static void 37 | bezctx_ps_moveto(bezctx *z, double x, double y, int is_open) { 38 | bezctx_ps *bc = (bezctx_ps *)z; 39 | 40 | if (!bc->is_open) fprintf(bc->f, "z\n"); 41 | fprintf(bc->f, "%g %g m\n", x, y); 42 | bc->is_open = is_open; 43 | bc->x = x; 44 | bc->y = y; 45 | } 46 | 47 | void 48 | bezctx_ps_lineto(bezctx *z, double x, double y) { 49 | bezctx_ps *bc = (bezctx_ps *)z; 50 | 51 | fprintf(bc->f, "%g %g l\n", x, y); 52 | bc->x = x; 53 | bc->y = y; 54 | } 55 | 56 | void 57 | bezctx_ps_quadto(bezctx *z, double xm, double ym, double x3, double y3) 58 | { 59 | bezctx_ps *bc = (bezctx_ps *)z; 60 | double x0, y0; 61 | double x1, y1; 62 | double x2, y2; 63 | 64 | x0 = bc->x; 65 | y0 = bc->y; 66 | x1 = xm + (1./3) * (x0 - xm); 67 | y1 = ym + (1./3) * (y0 - ym); 68 | x2 = xm + (1./3) * (x3 - xm); 69 | y2 = ym + (1./3) * (y3 - ym); 70 | fprintf(bc->f, "%g %g %g %g %g %g c\n", x1, y1, x2, y2, x3, y3); 71 | bc->x = x3; 72 | bc->y = y3; 73 | } 74 | 75 | void 76 | bezctx_ps_curveto(bezctx *z, double x1, double y1, double x2, double y2, 77 | double x3, double y3) 78 | { 79 | bezctx_ps *bc = (bezctx_ps *)z; 80 | 81 | fprintf(bc->f, "%g %g %g %g %g %g c\n", x1, y1, x2, y2, x3, y3); 82 | bc->x = x3; 83 | bc->y = y3; 84 | } 85 | 86 | bezctx * 87 | new_bezctx_ps(FILE *f) { 88 | bezctx_ps *result = znew(bezctx_ps, 1); 89 | 90 | result->base.moveto = bezctx_ps_moveto; 91 | result->base.lineto = bezctx_ps_lineto; 92 | result->base.quadto = bezctx_ps_quadto; 93 | result->base.curveto = bezctx_ps_curveto; 94 | result->base.mark_knot = NULL; 95 | result->is_open = 1; 96 | result->f = f; 97 | return &result->base; 98 | } 99 | 100 | void 101 | bezctx_ps_close(bezctx *z) 102 | { 103 | bezctx_ps *bc = (bezctx_ps *)z; 104 | 105 | if (!bc->is_open) fprintf(bc->f, "z\n"); 106 | zfree(bc); 107 | } 108 | -------------------------------------------------------------------------------- /ppedit/bezctx_ps.h: -------------------------------------------------------------------------------- 1 | 2 | const char *ps_prolog; 3 | const char *ps_postlog; 4 | 5 | bezctx *new_bezctx_ps(FILE *f); 6 | 7 | void 8 | bezctx_ps_close(bezctx *bc); 9 | -------------------------------------------------------------------------------- /ppedit/bezctx_quartz.c: -------------------------------------------------------------------------------- 1 | /* 2 | ppedit - A pattern plate editor for Spiro splines. 3 | Copyright (C) 2007 Raph Levien 4 | 5 | Licensed under the Apache License, Version 2.0 or the MIT license 7 | , at your 8 | option. This file may not be copied, modified, or distributed 9 | except according to those terms. 10 | 11 | */ 12 | #include 13 | 14 | #include "zmisc.h" 15 | #include "bezctx.h" 16 | #include "bezctx_quartz.h" 17 | 18 | 19 | typedef struct { 20 | bezctx base; 21 | CGMutablePathRef pathref; 22 | int is_open; 23 | } bezctx_quartz; 24 | 25 | static void 26 | bezctx_quartz_moveto(bezctx *z, double x, double y, int is_open) { 27 | bezctx_quartz *bc = (bezctx_quartz *)z; 28 | if (!bc->is_open) CGPathCloseSubpath(bc->pathref); 29 | CGPathMoveToPoint(bc->pathref, NULL, x, y); 30 | bc->is_open = is_open; 31 | } 32 | 33 | static void 34 | bezctx_quartz_lineto(bezctx *z, double x, double y) { 35 | bezctx_quartz *bc = (bezctx_quartz *)z; 36 | CGPathAddLineToPoint(bc->pathref, NULL, x, y); 37 | } 38 | 39 | static void 40 | bezctx_quartz_quadto(bezctx *z, double x1, double y1, double x2, double y2) 41 | { 42 | bezctx_quartz *bc = (bezctx_quartz *)z; 43 | CGPathAddQuadCurveToPoint(bc->pathref, NULL, x1, y1, x2, y2); 44 | } 45 | 46 | bezctx * 47 | new_bezctx_quartz(void) { 48 | bezctx_quartz *result = znew(bezctx_quartz, 1); 49 | 50 | result->base.moveto = bezctx_quartz_moveto; 51 | result->base.lineto = bezctx_quartz_lineto; 52 | result->base.quadto = bezctx_quartz_quadto; 53 | result->base.mark_knot = NULL; 54 | result->pathref = CGPathCreateMutable(); 55 | result->is_open = 1; 56 | return &result->base; 57 | } 58 | 59 | 60 | CGMutablePathRef 61 | bezctx_to_quartz(bezctx *z) 62 | { 63 | bezctx_quartz *bc = (bezctx_quartz *)z; 64 | CGMutablePathRef result = bc->pathref; 65 | 66 | if (!bc->is_open) CGPathCloseSubpath(result); 67 | zfree(bc); 68 | return result; 69 | } 70 | -------------------------------------------------------------------------------- /ppedit/bezctx_quartz.h: -------------------------------------------------------------------------------- 1 | bezctx *new_bezctx_quartz(void); 2 | 3 | CGMutablePathRef 4 | bezctx_to_quartz(bezctx *bc); 5 | -------------------------------------------------------------------------------- /ppedit/bezctx_x3.c: -------------------------------------------------------------------------------- 1 | /* 2 | ppedit - A pattern plate editor for Spiro splines. 3 | Copyright (C) 2007 Raph Levien 4 | 5 | Licensed under the Apache License, Version 2.0 or the MIT license 7 | , at your 8 | option. This file may not be copied, modified, or distributed 9 | except according to those terms. 10 | 11 | */ 12 | #include 13 | #include "zmisc.h" 14 | #include "bezctx.h" 15 | #include "bezctx_x3.h" 16 | 17 | typedef struct { 18 | bezctx base; 19 | x3dc *dc; 20 | int is_open; 21 | } bezctx_x3; 22 | 23 | static void 24 | bezctx_x3_moveto(bezctx *z, double x, double y, int is_open) { 25 | bezctx_x3 *bc = (bezctx_x3 *)z; 26 | 27 | if (!bc->is_open) x3closepath(bc->dc); 28 | x3moveto(bc->dc, x, y); 29 | bc->is_open = is_open; 30 | } 31 | 32 | void 33 | bezctx_x3_lineto(bezctx *z, double x, double y) { 34 | bezctx_x3 *bc = (bezctx_x3 *)z; 35 | 36 | x3lineto(bc->dc, x, y); 37 | } 38 | 39 | void 40 | bezctx_x3_quadto(bezctx *z, double x1, double y1, double x2, double y2) 41 | { 42 | bezctx_x3 *bc = (bezctx_x3 *)z; 43 | double x0, y0; 44 | 45 | x3getcurrentpoint(bc->dc, &x0, &y0); 46 | x3curveto(bc->dc, 47 | x1 + (1./3) * (x0 - x1), 48 | y1 + (1./3) * (y0 - y1), 49 | x1 + (1./3) * (x2 - x1), 50 | y1 + (1./3) * (y2 - y1), 51 | x2, 52 | y2); 53 | } 54 | 55 | void 56 | bezctx_x3_curveto(bezctx *z, double x1, double y1, double x2, double y2, 57 | double x3, double y3) 58 | { 59 | bezctx_x3 *bc = (bezctx_x3 *)z; 60 | 61 | x3curveto(bc->dc, x1, y1, x2, y2, x3, y3); 62 | } 63 | 64 | void 65 | bezctx_x3_finish(bezctx *z) 66 | { 67 | bezctx_x3 *bc = (bezctx_x3 *)z; 68 | 69 | if (!bc->is_open) 70 | x3closepath(bc->dc); 71 | 72 | zfree(bc); 73 | } 74 | 75 | bezctx * 76 | new_bezctx_x3(x3dc *dc) { 77 | bezctx_x3 *result = znew(bezctx_x3, 1); 78 | 79 | result->base.moveto = bezctx_x3_moveto; 80 | result->base.lineto = bezctx_x3_lineto; 81 | result->base.quadto = bezctx_x3_quadto; 82 | result->base.curveto = bezctx_x3_curveto; 83 | result->base.mark_knot = NULL; 84 | result->dc = dc; 85 | result->is_open = 1; 86 | return &result->base; 87 | } 88 | -------------------------------------------------------------------------------- /ppedit/bezctx_x3.h: -------------------------------------------------------------------------------- 1 | 2 | bezctx *new_bezctx_x3(x3dc *dc); 3 | void bezctx_x3_finish(bezctx *z); 4 | -------------------------------------------------------------------------------- /ppedit/carbon_main.c: -------------------------------------------------------------------------------- 1 | /* 2 | ppedit - A pattern plate editor for Spiro splines. 3 | Copyright (C) 2007 Raph Levien 4 | 5 | Licensed under the Apache License, Version 2.0 or the MIT license 7 | , at your 8 | option. This file may not be copied, modified, or distributed 9 | except according to those terms. 10 | 11 | */ 12 | #include 13 | 14 | #include "bezctx.h" 15 | #include "bezctx_quartz.h" 16 | #include "plate.h" 17 | #include "pe_view.h" 18 | 19 | #define kCommandToggleCorner 'togC' 20 | 21 | typedef struct { 22 | WindowRef window_ref; 23 | plate *p; 24 | 25 | HIViewRef view; 26 | } plate_edit; 27 | 28 | int n_iter = 10; 29 | 30 | pascal OSStatus my_handler(EventHandlerCallRef nextHandler, EventRef theEvent, void *data) 31 | { 32 | plate_edit *pe = (plate_edit *)data; 33 | WindowRef window = pe->window_ref; 34 | UInt32 klass = GetEventClass(theEvent); 35 | UInt32 kind = GetEventKind(theEvent); 36 | OSStatus result = eventNotHandledErr; 37 | OSStatus err; 38 | Point where; 39 | Rect bounds; 40 | 41 | switch (klass) { 42 | case kEventClassMouse: 43 | switch (kind) { 44 | case kEventMouseDown: 45 | err = GetEventParameter(theEvent, kEventParamMouseLocation, 46 | typeQDPoint, NULL, sizeof(where), NULL, &where); 47 | printf("mouse down %d %d\n", where.h, where.v); 48 | break; 49 | } 50 | break; 51 | case kEventClassWindow: 52 | switch (kind) { 53 | case kEventWindowDrawContent: 54 | printf("draw content\n"); 55 | result = noErr; 56 | break; 57 | case kEventWindowClickContentRgn: 58 | printf("click_content region\n"); 59 | break; 60 | case kEventWindowHandleContentClick: 61 | err = GetEventParameter(theEvent, kEventParamMouseLocation, 62 | typeQDPoint, NULL, sizeof(where), NULL, &where); 63 | GetWindowBounds(window, kWindowContentRgn, &bounds); 64 | printf("content click %d, %d; %d, %d\n", where.h, where.v, 65 | where.h - bounds.left, where.v - bounds.top); 66 | break; 67 | } 68 | } 69 | return result; 70 | } 71 | 72 | pascal OSStatus app_handler(EventHandlerCallRef nextHandler, EventRef theEvent, void *data) 73 | { 74 | plate_edit *pe = (plate_edit *)data; 75 | HICommand hiCommand; 76 | OSStatus result = eventNotHandledErr; 77 | GetEventParameter(theEvent, kEventParamDirectObject, typeHICommand,NULL, 78 | sizeof(HICommand),NULL,&hiCommand); 79 | unsigned int c = hiCommand.commandID; 80 | MenuRef menu; 81 | MenuItemIndex ix; 82 | 83 | printf("app_handler %c%c%c%c\n", 84 | (c >> 24) & 255, (c >> 16) & 255, (c >> 8) & 255, c & 255); 85 | switch (c) { 86 | case kHICommandUndo: 87 | GetIndMenuItemWithCommandID(NULL, kHICommandUndo, 1, &menu, &ix); 88 | SetMenuItemTextWithCFString(menu, ix, CFSTR("Undo disabled")); 89 | DisableMenuItem(menu, ix); 90 | break; 91 | case kCommandToggleCorner: 92 | pe_view_toggle_corner(pe->view); 93 | break; 94 | } 95 | return result; 96 | } 97 | 98 | void 99 | add_pe_view(WindowRef window, plate_edit *pe, plate *p) 100 | { 101 | HIRect rect = {{0, 0}, {32767, 32767}}; 102 | HIViewRef view; 103 | 104 | pe_view_create(window, &rect, &view); 105 | pe_view_set_plate(view, p); 106 | HIViewSetVisible(view, true); 107 | pe->view = view; 108 | } 109 | 110 | void 111 | init_window(WindowRef window, plate_edit *pe) 112 | { 113 | EventTypeSpec app_event_types[] = { 114 | { kEventClassCommand, kEventProcessCommand } 115 | }; 116 | EventTypeSpec event_types[] = { 117 | { kEventClassWindow, kEventWindowDrawContent }, 118 | { kEventClassWindow, kEventWindowHandleContentClick }, 119 | { kEventClassMouse, kEventMouseDown } 120 | }; 121 | 122 | InstallApplicationEventHandler(NewEventHandlerUPP(app_handler), 123 | GetEventTypeCount(app_event_types), 124 | app_event_types, (void *)pe, NULL); 125 | InstallWindowEventHandler(window, NewEventHandlerUPP(my_handler), 126 | GetEventTypeCount(event_types), 127 | event_types, (void *)pe, NULL); 128 | 129 | add_pe_view(window, pe, pe->p); 130 | } 131 | 132 | int main(int argc, char* argv[]) 133 | { 134 | IBNibRef nibRef; 135 | WindowRef window; 136 | plate_edit pe; 137 | 138 | OSStatus err; 139 | 140 | // Create a Nib reference passing the name of the nib file (without the .nib extension) 141 | // CreateNibReference only searches into the application bundle. 142 | err = CreateNibReference(CFSTR("main"), &nibRef); 143 | require_noerr( err, CantGetNibRef ); 144 | 145 | // Once the nib reference is created, set the menu bar. "MainMenu" is the name of the menu bar 146 | // object. This name is set in InterfaceBuilder when the nib is created. 147 | err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar")); 148 | require_noerr( err, CantSetMenuBar ); 149 | 150 | // Then create a window. "MainWindow" is the name of the window object. This name is set in 151 | // InterfaceBuilder when the nib is created. 152 | err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &window); 153 | require_noerr( err, CantCreateWindow ); 154 | 155 | // We don't need the nib reference anymore. 156 | DisposeNibReference(nibRef); 157 | 158 | pe.window_ref = window; 159 | pe.p = file_read_plate("/Users/raph/golf/ppedit/g.plate"); 160 | if (pe.p == NULL) 161 | pe.p = new_plate(); 162 | init_window(window, &pe); 163 | // The window was created hidden so show it. 164 | ShowWindow( window ); 165 | 166 | // Call the event loop 167 | RunApplicationEventLoop(); 168 | 169 | CantCreateWindow: 170 | CantSetMenuBar: 171 | CantGetNibRef: 172 | return err; 173 | } 174 | -------------------------------------------------------------------------------- /ppedit/cornu.h: -------------------------------------------------------------------------------- 1 | void 2 | cornu_to_bpath(const double xs[], const double ys[], const double ths[], int n, 3 | bezctx *bc, double tol, int closed, int kt0, int n_kt); 4 | 5 | void 6 | local_ths(const double xs[], const double ys[], double ths[], int n, int closed); 7 | 8 | void 9 | endpoint_ths(const double xs[], const double ys[], double ths[], int n); 10 | 11 | void 12 | tweak_ths(const double xs[], const double ys[], double ths[], int n, 13 | double delt, int closed); 14 | -------------------------------------------------------------------------------- /ppedit/image.c: -------------------------------------------------------------------------------- 1 | /* 2 | ppedit - A pattern plate editor for Spiro splines. 3 | Copyright (C) 2007 Raph Levien 4 | 5 | Licensed under the Apache License, Version 2.0 or the MIT license 7 | , at your 8 | option. This file may not be copied, modified, or distributed 9 | except according to those terms. 10 | 11 | */ 12 | #include 13 | #include 14 | #include "zmisc.h" 15 | #include "image.h" 16 | 17 | /* An image loaded into memory. */ 18 | struct _image { 19 | unsigned char *buf; 20 | int width; 21 | int height; 22 | int rowstride; 23 | }; 24 | 25 | static image * 26 | load_ppm_file(FILE *f, char **reason) 27 | { 28 | image *result; 29 | char line[256]; 30 | int xs, ys; 31 | int depth; 32 | int n; 33 | 34 | fseek(f, 0, SEEK_SET); 35 | fgets(line, sizeof(line), f); 36 | do { 37 | fgets(line, sizeof(line), f); 38 | } while (line[0] == '#'); 39 | n = sscanf(line, "%d %d", &xs, &ys); 40 | if (n != 2) { 41 | *reason = "Error reading ppmraw size line"; 42 | fclose(f); 43 | return NULL; 44 | } 45 | do { 46 | fgets(line, sizeof(line), f); 47 | } while (line[0] == '#'); 48 | n = sscanf(line, "%d", &depth); 49 | if (n != 1) { 50 | *reason = "Error reading ppmraw depth line"; 51 | fclose(f); 52 | return NULL; 53 | } 54 | result = znew(image, 1); 55 | result->rowstride = 3 * xs; 56 | result->buf = zalloc(ys * result->rowstride); 57 | result->width = xs; 58 | result->height = ys; 59 | fread(result->buf, 1, ys * result->rowstride, f); 60 | fclose(f); 61 | return result; 62 | } 63 | 64 | image * 65 | load_image_file(const char *fn, char **reason) 66 | { 67 | FILE *f = fopen(fn, "rb"); 68 | unsigned char buf[256]; 69 | int n; 70 | 71 | if (f == NULL) { 72 | *reason = "Error opening file"; 73 | return NULL; 74 | } 75 | n = fread(buf, 1, sizeof(buf), f); 76 | if (n < 4) { 77 | *reason = "Short file"; 78 | fclose(f); 79 | return NULL; 80 | } 81 | if (buf[0] != 'P' || buf[1] != '6') { 82 | *reason = "Unrecognized magic"; 83 | fclose(f); 84 | return NULL; 85 | } 86 | return load_ppm_file(f, reason); 87 | } 88 | 89 | void 90 | free_image(image *im) 91 | { 92 | zfree(im->buf); 93 | zfree(im); 94 | } 95 | 96 | void 97 | render_image(image *im, const double affine[6], 98 | unsigned char *buf, int rowstride, int x0, int y0, int x1, int y1) 99 | { 100 | int y; 101 | unsigned char *dest_line = buf; 102 | int src_x0 = x0; 103 | 104 | for (y = y0; y < y1; y++) { 105 | int src_y = y; 106 | 107 | if (src_y >= 0 && src_y < im->height) { 108 | unsigned char *img_line = im->buf + src_y * im->rowstride; 109 | int left_pad = -src_x0; 110 | int img_run, img_off, right_pad; 111 | 112 | if (left_pad > x1 - x0) left_pad = x1 - x0; 113 | if (left_pad > 0) { 114 | memset(dest_line, 255, 3 * left_pad); 115 | } else left_pad = 0; 116 | img_off = src_x0; 117 | if (img_off < 0) img_off = 0; 118 | img_run = x1 - x0 - left_pad; 119 | if (img_run > im->width - img_off) img_run = im->width - img_off; 120 | if (img_run > 0) { 121 | memcpy(dest_line + 3 * left_pad, img_line + 3 * img_off, 3 * img_run); 122 | } else img_run = 0; 123 | right_pad = x1 - x0 - left_pad - img_run; 124 | if (right_pad > 0) { 125 | memset(dest_line + 3 * (left_pad + img_run), 255, 3 * right_pad); 126 | } 127 | } else { 128 | memset(dest_line, 255, rowstride); 129 | } 130 | dest_line += rowstride; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /ppedit/image.h: -------------------------------------------------------------------------------- 1 | typedef struct _image image; 2 | 3 | image * 4 | load_image_file(const char *fn, char **reason); 5 | 6 | void 7 | free_image(image *im); 8 | 9 | void 10 | render_image(image *im, const double affine[6], 11 | unsigned char *buf, int rowstride, int x0, int y0, int x1, int y1); 12 | -------------------------------------------------------------------------------- /ppedit/pe_view.h: -------------------------------------------------------------------------------- 1 | OSStatus pe_view_create( 2 | WindowRef inWindow, 3 | const HIRect* inBounds, 4 | HIViewRef* outView); 5 | 6 | void 7 | pe_view_set_plate(HIViewRef view, plate *p); 8 | 9 | void 10 | pe_view_toggle_corner(HIViewRef view); 11 | 12 | -------------------------------------------------------------------------------- /ppedit/plate.c: -------------------------------------------------------------------------------- 1 | /* 2 | ppedit - A pattern plate editor for Spiro splines. 3 | Copyright (C) 2007 Raph Levien 4 | 5 | Licensed under the Apache License, Version 2.0 or the MIT license 7 | , at your 8 | option. This file may not be copied, modified, or distributed 9 | except according to those terms. 10 | 11 | */ 12 | #include 13 | #include 14 | #include 15 | 16 | #include "zmisc.h" 17 | #include "sexp.h" 18 | #include "bezctx_intf.h" 19 | #include "bezctx_hittest.h" 20 | #include "cornu.h" 21 | #include "spiro.h" 22 | #include "plate.h" 23 | 24 | /* This is a global while we're playing with the tangent solver. Once we get that 25 | nailed down, it will go away. */ 26 | extern int n_iter; 27 | 28 | /** 29 | * These are functions for editing a Cornu spline ("plate"), intended 30 | * to be somewhat independent of the UI toolkit specifics. 31 | **/ 32 | 33 | plate * 34 | new_plate(void) 35 | { 36 | plate *p = znew(plate, 1); 37 | 38 | p->n_sp = 0; 39 | p->n_sp_max = 4; 40 | p->sp = znew(subpath, p->n_sp_max); 41 | p->mmode = MOUSE_MODE_ADD_CURVE; 42 | p->last_curve_mmode = p->mmode; 43 | return p; 44 | } 45 | 46 | void 47 | free_plate(plate *p) 48 | { 49 | int i; 50 | for (i = 0; i < p->n_sp; i++) { 51 | subpath *sp = &p->sp[i]; 52 | zfree(sp->kt); 53 | } 54 | zfree(p->sp); 55 | zfree(p); 56 | } 57 | 58 | plate * 59 | copy_plate(const plate *p) 60 | { 61 | int i; 62 | plate *n = znew(plate, 1); 63 | 64 | n->n_sp = p->n_sp; 65 | n->n_sp_max = p->n_sp_max; 66 | n->sp = znew(subpath, n->n_sp_max); 67 | for (i = 0; i < n->n_sp; i++) { 68 | subpath *sp = &p->sp[i]; 69 | subpath *nsp = &n->sp[i]; 70 | 71 | nsp->n_kt = sp->n_kt; 72 | nsp->n_kt_max = sp->n_kt_max; 73 | nsp->kt = znew(knot, nsp->n_kt_max); 74 | memcpy(nsp->kt, sp->kt, nsp->n_kt * sizeof(knot)); 75 | nsp->closed = sp->closed; 76 | } 77 | n->mmode = p->mmode; 78 | return n; 79 | } 80 | 81 | void 82 | plate_select_all(plate *p, int selected) 83 | { 84 | int i, j; 85 | 86 | /* find an existing point to select, if any */ 87 | for (i = 0; i < p->n_sp; i++) { 88 | subpath *sp = &p->sp[i]; 89 | 90 | for (j = 0; j < sp->n_kt; j++) { 91 | knot *kt = &sp->kt[j]; 92 | kt->flags &= ~KT_SELECTED; 93 | if (selected) 94 | kt->flags |= KT_SELECTED; 95 | } 96 | } 97 | } 98 | 99 | subpath * 100 | plate_find_selected_sp(plate *p) 101 | { 102 | int i, j; 103 | 104 | /* find an existing point to select, if any */ 105 | for (i = 0; i < p->n_sp; i++) { 106 | subpath *sp = &p->sp[i]; 107 | 108 | for (j = 0; j < sp->n_kt; j++) { 109 | knot *kt = &sp->kt[j]; 110 | if (kt->flags & KT_SELECTED) 111 | return sp; 112 | } 113 | } 114 | return NULL; 115 | } 116 | 117 | subpath * 118 | plate_new_sp(plate *p) 119 | { 120 | subpath *sp; 121 | if (p->n_sp == p->n_sp_max) 122 | p->sp = zrenew(subpath, p->sp, p->n_sp_max <<= 1); 123 | sp = &p->sp[p->n_sp++]; 124 | sp->n_kt = 0; 125 | sp->n_kt_max = 4; 126 | sp->kt = znew(knot, sp->n_kt_max); 127 | sp->closed = 0; 128 | return sp; 129 | } 130 | 131 | static int 132 | try_close_sp(subpath *sp, int ix, int force) 133 | { 134 | int n_kt = sp->n_kt; 135 | 136 | if (sp->closed) return 0; 137 | if (n_kt < 3) return 0; 138 | if (!force) { 139 | if (ix != 0 && ix != n_kt - 1) return 0; 140 | if (!(sp->kt[n_kt - 1 - ix].flags & KT_SELECTED)) return 0; 141 | } 142 | sp->closed = 1; 143 | return 1; 144 | } 145 | 146 | void 147 | plate_press(plate *p, double x, double y, press_mod mods) 148 | { 149 | int i, j; 150 | subpath *sp; 151 | knot *kt; 152 | const double srad = 5; 153 | kt_flags new_kt_flags = KT_SELECTED; 154 | 155 | if (p->mmode == MOUSE_MODE_ADD_CORNER) 156 | new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_OPEN : KT_CORNER; 157 | else if (p->mmode == MOUSE_MODE_ADD_CORNU) 158 | new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_CORNU; 159 | else if (p->mmode == MOUSE_MODE_ADD_LEFT) 160 | new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_LEFT; 161 | else if (p->mmode == MOUSE_MODE_ADD_RIGHT) 162 | new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_RIGHT; 163 | else 164 | new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_OPEN; 165 | 166 | p->x0 = x; 167 | p->y0 = y; 168 | 169 | /* find an existing point to select, if any */ 170 | for (i = 0; i < p->n_sp; i++) { 171 | sp = &p->sp[i]; 172 | 173 | for (j = 0; j < sp->n_kt; j++) { 174 | kt = &sp->kt[j]; 175 | if (hypot(kt->x - x, kt->y - y) < srad) { 176 | int was_closed = try_close_sp(sp, j, mods & PRESS_MOD_DOUBLE); 177 | if (mods & PRESS_MOD_SHIFT) { 178 | kt->flags ^= KT_SELECTED; 179 | } else if (!(kt->flags & KT_SELECTED)) { 180 | plate_select_all(p, 0); 181 | kt->flags |= KT_SELECTED; 182 | } 183 | p->description = was_closed ? "Close Path" : NULL; 184 | p->motmode = MOTION_MODE_MOVE; 185 | return; 186 | } 187 | } 188 | } 189 | 190 | if (p->mmode == MOUSE_MODE_ADD_RIGHT || p->mmode == MOUSE_MODE_ADD_LEFT) 191 | p->mmode = p->last_curve_mmode; 192 | 193 | 194 | #if 1 195 | /* test whether the button press was on a curve; if so, insert point */ 196 | for (i = 0; i < p->n_sp; i++) { 197 | bezctx *bc = new_bezctx_hittest(x, y); 198 | int knot_idx; 199 | 200 | sp = &p->sp[i]; 201 | free_spiro(draw_subpath(sp, bc)); 202 | if (bezctx_hittest_report(bc, &knot_idx) < srad) { 203 | knot *kt; 204 | 205 | if (sp->n_kt == sp->n_kt_max) 206 | sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1); 207 | plate_select_all(p, 0); 208 | kt = &sp->kt[knot_idx + 1]; 209 | memmove(&kt[1], kt, (sp->n_kt - knot_idx - 1) * sizeof(knot)); 210 | sp->n_kt++; 211 | kt->x = x; 212 | kt->y = y; 213 | kt->flags = new_kt_flags; 214 | p->description = "Insert Point"; 215 | p->motmode = MOTION_MODE_MOVE; 216 | return; 217 | } 218 | } 219 | #endif 220 | 221 | if (p->mmode == MOUSE_MODE_SELECT) { 222 | plate_select_all(p, 0); 223 | p->sel_x0 = x; 224 | p->sel_y0 = y; 225 | p->motmode = MOTION_MODE_SELECT; 226 | return; 227 | } 228 | 229 | sp = plate_find_selected_sp(p); 230 | if (sp == NULL || sp->closed) { 231 | sp = plate_new_sp(p); 232 | p->description = p->n_sp > 1 ? "New Subpath" : "New Path"; 233 | } 234 | 235 | if (sp->n_kt == sp->n_kt_max) 236 | sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1); 237 | plate_select_all(p, 0); 238 | kt = &sp->kt[sp->n_kt++]; 239 | kt->x = x; 240 | kt->y = y; 241 | kt->flags = new_kt_flags; 242 | if (p->description == NULL) 243 | p->description = "Add Point"; 244 | p->motmode = MOTION_MODE_MOVE; 245 | } 246 | 247 | void 248 | plate_motion_move(plate *p, double x, double y) 249 | { 250 | int i, j, n = 0; 251 | double dx, dy; 252 | 253 | dx = x - p->x0; 254 | dy = y - p->y0; 255 | p->x0 = x; 256 | p->y0 = y; 257 | 258 | for (i = 0; i < p->n_sp; i++) { 259 | subpath *sp = &p->sp[i]; 260 | 261 | for (j = 0; j < sp->n_kt; j++) { 262 | knot *kt = &sp->kt[j]; 263 | if (kt->flags & KT_SELECTED) { 264 | kt->x += dx; 265 | kt->y += dy; 266 | n++; 267 | } 268 | } 269 | } 270 | p->description = n == 1 ? "Move Point" : "Move Points"; 271 | } 272 | 273 | void 274 | plate_motion_select(plate *p, double x1, double y1) 275 | { 276 | int i, j; 277 | double x0 = p->sel_x0; 278 | double y0 = p->sel_y0; 279 | 280 | #ifdef VERBOSE 281 | printf("plate_motion_select %g %g\n", x1, y1); 282 | #endif 283 | p->x0 = x1; 284 | p->y0 = y1; 285 | 286 | if (x0 > x1) { 287 | double tmp = x1; 288 | x1 = x0; 289 | x0 = tmp; 290 | } 291 | if (y0 > y1) { 292 | double tmp = y1; 293 | y1 = y0; 294 | y0 = tmp; 295 | } 296 | 297 | for (i = 0; i < p->n_sp; i++) { 298 | subpath *sp = &p->sp[i]; 299 | 300 | for (j = 0; j < sp->n_kt; j++) { 301 | knot *kt = &sp->kt[j]; 302 | kt->flags &= ~KT_SELECTED; 303 | if (kt->x >= x0 && kt->x <= x1 && 304 | kt->y >= y0 && kt->y <= y1) 305 | kt->flags |= KT_SELECTED; 306 | } 307 | } 308 | } 309 | 310 | void plate_unpress(plate *p) 311 | { 312 | p->motmode = MOTION_MODE_IDLE; 313 | } 314 | 315 | void 316 | plate_toggle_corner(plate *p) 317 | { 318 | int i, j; 319 | 320 | /* find an existing point to select, if any */ 321 | for (i = 0; i < p->n_sp; i++) { 322 | subpath *sp = &p->sp[i]; 323 | 324 | for (j = 0; j < sp->n_kt; j++) { 325 | knot *kt = &sp->kt[j]; 326 | if (kt->flags & KT_SELECTED) { 327 | if (kt->flags & KT_CORNER) { 328 | kt->flags |= KT_OPEN; 329 | kt->flags &= ~KT_CORNER; 330 | } else { 331 | kt->flags &= ~KT_OPEN; 332 | kt->flags |= KT_CORNER; 333 | } 334 | } 335 | } 336 | } 337 | } 338 | 339 | void 340 | plate_delete_pt(plate *p) 341 | { 342 | int i, j; 343 | 344 | /* find an existing point to select, if any */ 345 | for (i = 0; i < p->n_sp; i++) { 346 | subpath *sp = &p->sp[i]; 347 | 348 | for (j = 0; j < sp->n_kt; j++) { 349 | knot *kt = &sp->kt[j]; 350 | if (kt->flags & KT_SELECTED) { 351 | memmove(kt, &kt[1], (sp->n_kt - j - 1) * sizeof(knot)); 352 | sp->n_kt--; 353 | if (sp->n_kt < 3) sp->closed = 0; 354 | j--; 355 | } 356 | } 357 | } 358 | } 359 | 360 | /* Note: caller is responsible for freeing returned spiro_seg. */ 361 | spiro_seg * 362 | draw_subpath(const subpath *sp, bezctx *bc) 363 | { 364 | int n = sp->n_kt; 365 | int i; 366 | spiro_cp *path; 367 | spiro_seg *s = NULL; 368 | 369 | if (n > 1) { 370 | path = znew(spiro_cp, n); 371 | 372 | for (i = 0; i < n; i++) { 373 | kt_flags flags = sp->kt[i].flags; 374 | path[i].x = sp->kt[i].x; 375 | path[i].y = sp->kt[i].y; 376 | path[i].ty = !sp->closed && i == 0 ? '{' : 377 | !sp->closed && i == n - 1 ? '}' : 378 | (flags & KT_OPEN) ? 'o' : 379 | (flags & KT_LEFT) ? '[' : 380 | (flags & KT_RIGHT) ? ']' : 381 | (flags & KT_CORNU) ? 'c' : 382 | 'v'; 383 | } 384 | 385 | s = run_spiro(path, n); 386 | spiro_to_bpath(s, n, bc); 387 | 388 | zfree(path); 389 | } 390 | return s; 391 | } 392 | 393 | int 394 | file_write_plate(const char *fn, const plate *p) 395 | { 396 | FILE *f = fopen(fn, "w"); 397 | int i, j; 398 | int st; 399 | 400 | if (f == NULL) 401 | return -1; 402 | st = fprintf(f, "(plate\n"); 403 | for (i = 0; i < p->n_sp; i++) { 404 | subpath *sp = &p->sp[i]; 405 | for (j = 0; j < sp->n_kt; j++) { 406 | kt_flags kf = sp->kt[j].flags; 407 | const char *cmd; 408 | 409 | if (kf & KT_OPEN) cmd = "o"; 410 | else if (kf & KT_CORNER) cmd = "v"; 411 | else if (kf & KT_CORNU) cmd = "c"; 412 | else if (kf & KT_LEFT) cmd = "["; 413 | else if (kf & KT_RIGHT) cmd = "]"; 414 | st = fprintf(f, " (%s %g %g)\n", cmd, sp->kt[j].x, sp->kt[j].y); 415 | if (st < 0) break; 416 | } 417 | if (st < 0) break; 418 | if (sp->closed) { 419 | st = fprintf(f, " (z)\n"); 420 | } 421 | if (st < 0) break; 422 | } 423 | if (st >= 0) 424 | st = fprintf(f, ")\n"); 425 | if (st >= 0) 426 | st = fclose(f); 427 | return st < 0 ? -1 : 0; 428 | } 429 | 430 | static int 431 | file_read_plate_inner(sexp_reader *sr, plate *p) 432 | { 433 | subpath *sp = NULL; 434 | 435 | sexp_token(sr); 436 | if (sr->singlechar != '(') return -1; 437 | sexp_token(sr); 438 | if (strcmp(sr->tokbuf, "plate")) return -1; 439 | for (;;) { 440 | sexp_token(sr); 441 | if (sr->singlechar == ')') break; 442 | else if (sr->singlechar == '(') { 443 | int cmd; 444 | 445 | sexp_token(sr); 446 | cmd = sr->singlechar; 447 | if (cmd == 'o' || cmd == 'v' || cmd == '[' || cmd == ']' || 448 | cmd == 'c') { 449 | double x, y; 450 | knot *kt; 451 | 452 | sexp_token(sr); 453 | if (!sr->is_double) return -1; 454 | x = sr->d; 455 | sexp_token(sr); 456 | if (!sr->is_double) return -1; 457 | y = sr->d; 458 | sexp_token(sr); 459 | if (sr->singlechar != ')') return -1; 460 | 461 | if (sp == NULL || sp->closed) 462 | sp = plate_new_sp(p); 463 | 464 | if (sp->n_kt == sp->n_kt_max) 465 | sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1); 466 | kt = &sp->kt[sp->n_kt++]; 467 | kt->x = x; 468 | kt->y = y; 469 | switch (cmd) { 470 | case 'o': 471 | kt->flags = KT_OPEN; 472 | break; 473 | case '[': 474 | kt->flags = KT_LEFT; 475 | break; 476 | case ']': 477 | kt->flags = KT_RIGHT; 478 | break; 479 | case 'c': 480 | kt->flags = KT_CORNU; 481 | break; 482 | default: 483 | kt->flags = KT_CORNER; 484 | break; 485 | } 486 | } else if (cmd == 'z') { 487 | if (sp == NULL) return -1; 488 | sp->closed = 1; 489 | sexp_token(sr); 490 | if (sr->singlechar != ')') return -1; 491 | } else 492 | return -1; 493 | } else return -1; 494 | } 495 | return 0; 496 | } 497 | 498 | plate * 499 | file_read_plate(const char *fn) 500 | { 501 | FILE *f = fopen(fn, "r"); 502 | plate *p; 503 | sexp_reader sr; 504 | 505 | if (f == NULL) 506 | return NULL; 507 | sr.f = f; 508 | p = new_plate(); 509 | if (file_read_plate_inner(&sr, p)) { 510 | free_plate(p); 511 | p = NULL; 512 | } 513 | fclose(f); 514 | p->mmode = MOUSE_MODE_SELECT; 515 | p->motmode = MOTION_MODE_IDLE; 516 | return p; 517 | } 518 | -------------------------------------------------------------------------------- /ppedit/plate.h: -------------------------------------------------------------------------------- 1 | typedef enum { 2 | KT_OPEN = 1, 3 | KT_CORNER = 2, 4 | KT_LEFT = 4, 5 | KT_RIGHT = 8, 6 | KT_CORNU = 16, 7 | KT_SELECTED = 256 8 | } kt_flags; 9 | 10 | typedef struct { 11 | double x; 12 | double y; 13 | kt_flags flags; 14 | } knot; 15 | 16 | typedef struct { 17 | int n_kt; 18 | int n_kt_max; 19 | knot *kt; 20 | int closed; 21 | } subpath; 22 | 23 | typedef enum { 24 | MOUSE_MODE_SELECT, 25 | MOUSE_MODE_ADD_CURVE, 26 | MOUSE_MODE_ADD_CORNER, 27 | MOUSE_MODE_ADD_CORNU, 28 | MOUSE_MODE_ADD_LEFT, 29 | MOUSE_MODE_ADD_RIGHT 30 | } mouse_mode; 31 | 32 | typedef enum { 33 | MOTION_MODE_IDLE, 34 | MOTION_MODE_SELECT, 35 | MOTION_MODE_MOVE 36 | } motion_mode; 37 | 38 | typedef struct { 39 | double x0, y0; 40 | const char *description; 41 | 42 | int n_sp; 43 | int n_sp_max; 44 | subpath *sp; 45 | mouse_mode mmode; 46 | mouse_mode last_curve_mmode; 47 | motion_mode motmode; 48 | double sel_x0, sel_y0; 49 | } plate; 50 | 51 | typedef enum { 52 | PRESS_MOD_SHIFT = 1, 53 | PRESS_MOD_CTRL = 2, 54 | PRESS_MOD_DOUBLE = 4, 55 | PRESS_MOD_TRIPLE = 8 56 | } press_mod; 57 | 58 | plate * 59 | new_plate(void); 60 | 61 | void 62 | free_plate(plate *p); 63 | 64 | plate * 65 | copy_plate(const plate *p); 66 | 67 | void 68 | plate_select_all(plate *p, int selected); 69 | 70 | subpath * 71 | plate_find_selected_sp(plate *p); 72 | 73 | subpath * 74 | plate_new_sp(plate *p); 75 | 76 | void 77 | plate_press(plate *p, double x, double y, press_mod mods); 78 | 79 | void 80 | plate_motion_move(plate *p, double x, double y); 81 | 82 | void 83 | plate_motion_select(plate *p, double x, double y); 84 | 85 | void plate_unpress(plate *p); 86 | 87 | void 88 | plate_toggle_corner(plate *p); 89 | 90 | void 91 | plate_delete_pt(plate *p); 92 | 93 | spiro_seg * 94 | draw_subpath(const subpath *sp, bezctx *bc); 95 | 96 | int 97 | file_write_plate(const char *fn, const plate *p); 98 | 99 | plate * 100 | file_read_plate(const char *fn); 101 | -------------------------------------------------------------------------------- /ppedit/sexp.c: -------------------------------------------------------------------------------- 1 | /* 2 | ppedit - A pattern plate editor for Spiro splines. 3 | Copyright (C) 2007 Raph Levien 4 | 5 | Licensed under the Apache License, Version 2.0 or the MIT license 7 | , at your 8 | option. This file may not be copied, modified, or distributed 9 | except according to those terms. 10 | 11 | */ 12 | #include 13 | #include 14 | 15 | #include "sexp.h" 16 | 17 | /* This is handcoded to avoid locale problems. */ 18 | static int 19 | parse_double(sexp_reader *sr) 20 | { 21 | double sign = 1.0, val = 0.0; 22 | int i; 23 | int numstart; 24 | const char * const b = sr->tokbuf; 25 | int is_double = 1; 26 | 27 | i = 0; 28 | if (b[i] == '+') { 29 | i++; 30 | } else if (b[i] == '-') { 31 | sign = -1.0; 32 | i++; 33 | } 34 | numstart = i; 35 | while (b[i] >= '0' && b[i] <= '9') 36 | val = val * 10.0 + b[i++] - '0'; 37 | if (b[i] == '.') { 38 | double frac = 1.0; 39 | 40 | for (i++; b[i] >= '0' && b[i] <= '9'; i++) { 41 | frac *= 0.1; 42 | val += (b[i] - '0') * frac; 43 | } 44 | 45 | /* A '.' without any digits on either side isn't valid. */ 46 | if (i == numstart + 1) 47 | is_double = 0; 48 | } 49 | if (b[i] == 'e' || b[i] == 'E') { 50 | int expsign = 1, exp = 0; 51 | int expstart; 52 | 53 | if (b[i] == '+') { 54 | i++; 55 | } else if (b[i] == '-') { 56 | expsign = -1; 57 | i++; 58 | } 59 | expstart = i; 60 | while (b[i] >= '0' && b[i] <= '9') 61 | exp = exp * 10 + b[i++] - '0'; 62 | 63 | if (i == expstart) 64 | is_double = 0; 65 | val *= pow(10.0, expsign * exp); 66 | } 67 | val *= sign; 68 | sr->d = val; 69 | if (b[i] != 0) is_double = 0; 70 | sr->is_double = is_double; 71 | return is_double; 72 | } 73 | 74 | /* Return values: 0 = EOF, 1 = token but not double, 2 = valid double */ 75 | int 76 | sexp_token(sexp_reader *sr) 77 | { 78 | int c; 79 | int i; 80 | 81 | sr->singlechar = -1; 82 | if (sr->f == NULL) 83 | return 0; 84 | 85 | for (;;) { 86 | c = getc(sr->f); 87 | if (c == EOF) { 88 | sr->f = NULL; 89 | return 0; 90 | } else if (c == '#') { 91 | do { 92 | c = getc(sr->f); 93 | } while (c != EOF && c != '\r' && c != '\n'); 94 | } else if (c != ' ' && c != '\r' && c != '\n' && c != '\t') 95 | break; 96 | } 97 | sr->tokbuf[0] = c; 98 | i = 1; 99 | if (c != '(' && c != ')') { 100 | for (;;) { 101 | c = getc(sr->f); 102 | if (c == EOF) { 103 | sr->f = NULL; 104 | break; 105 | } else if (c == ' ' || c == '\r' || c == '\n' || c == '\t') { 106 | break; 107 | } else if (c == '(' || c == ')' || c == '#') { 108 | ungetc(c, sr->f); 109 | break; 110 | } else if (i < sizeof(sr->tokbuf) - 1) 111 | sr->tokbuf[i++] = c; 112 | } 113 | } 114 | sr->tokbuf[i] = 0; 115 | if (i == 1) 116 | sr->singlechar = sr->tokbuf[0]; 117 | return 1 + parse_double(sr); 118 | } 119 | -------------------------------------------------------------------------------- /ppedit/sexp.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | FILE *f; 3 | char tokbuf[256]; 4 | int singlechar; 5 | int is_double; 6 | double d; 7 | } sexp_reader; 8 | 9 | int 10 | sexp_token(sexp_reader *sr); 11 | -------------------------------------------------------------------------------- /ppedit/spiro.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | double x; 3 | double y; 4 | char ty; 5 | } spiro_cp; 6 | 7 | typedef struct spiro_seg_s spiro_seg; 8 | 9 | spiro_seg * 10 | run_spiro(const spiro_cp *src, int n); 11 | 12 | void 13 | free_spiro(spiro_seg *s); 14 | 15 | void 16 | spiro_to_bpath(const spiro_seg *s, int n, bezctx *bc); 17 | 18 | double get_knot_th(const spiro_seg *s, int i); 19 | -------------------------------------------------------------------------------- /ppedit/zmisc.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Misc portability and convenience macros. 3 | **/ 4 | 5 | #include 6 | 7 | #define zalloc malloc 8 | #define zrealloc realloc 9 | #define zfree free 10 | 11 | #define znew(type, n) (type *)zalloc(sizeof(type) * (n)) 12 | #define zrenew(type, p, n) (type *)zrealloc((p), sizeof(type) * (n)) 13 | -------------------------------------------------------------------------------- /x3/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = gtk 2 | 3 | ifeq ($(TARGET),gtk) 4 | X3_PLAT = X3_GTK 5 | X3_INCL = `pkg-config --cflags gtk+-2.0` 6 | X3_LIBS = `pkg-config --libs gtk+-2.0` 7 | endif 8 | 9 | ifeq ($(TARGET),carbon) 10 | X3_PLAT = X3_CARBON 11 | X3_LIBS = -framework Carbon 12 | endif 13 | 14 | ifeq ($(TARGET),win32) 15 | X3_PLAT = X3_WIN32 16 | X3_LIBS = -lgdi32 17 | endif 18 | 19 | CFLAGS = -g -Wall -D$(X3_PLAT) $(X3_INCL) 20 | LDFLAGS = -g 21 | LDLIBS = $(X3_LIBS) 22 | 23 | test: test.o x3$(TARGET).o x3common.o 24 | 25 | x3lisp.o: x3lisp.c 26 | -------------------------------------------------------------------------------- /x3/pyrex/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = gtk 2 | 3 | ifeq ($(TARGET),gtk) 4 | X3_PLAT = X3_GTK 5 | X3_INCL = `pkg-config --cflags gtk+-2.0` 6 | X3_LIBS = `pkg-config --libs gtk+-2.0` 7 | SHARED_FLAG = -shared 8 | endif 9 | 10 | ifeq ($(TARGET),carbon) 11 | X3_PLAT = X3_CARBON 12 | X3_LIBS = -framework Carbon 13 | SHARED_FLAG = -dynamiclib -flat_namespace -undefined suppress 14 | endif 15 | 16 | ifeq ($(TARGET),win32) 17 | X3_PLAT = X3_WIN32 18 | X3_LIBS = -lgdi32 19 | endif 20 | 21 | PY_INCL := -I$(shell python -c "import distutils.sysconfig; print distutils.sysconfig.get_python_inc()") 22 | 23 | CFLAGS = -g -Wall -fPIC -I.. $(PY_INCL) -D$(X3_PLAT) $(X3_INCL) 24 | LDFLAGS = -g 25 | LDLIBS = $(X3_LIBS) 26 | 27 | x3.so: x3.o x3$(TARGET).o x3common.o 28 | gcc $(SHARED_FLAG) $^ $(X3_LIBS) -o $@ 29 | 30 | x3.c: x3.pyx 31 | python2.4-pyrexc $< 32 | 33 | x3$(TARGET).o: ../x3$(TARGET).c 34 | $(CC) -c $(CFLAGS) -o $@ $< 35 | 36 | x3common.o: ../x3common.c 37 | $(CC) -c $(CFLAGS) -o $@ $< 38 | 39 | 40 | -------------------------------------------------------------------------------- /x3/pyrex/beztest.py: -------------------------------------------------------------------------------- 1 | import x3 2 | from math import * 3 | 4 | def my_callback(cmd, what, arg, more): 5 | print cmd, what, arg 6 | 7 | class bez: 8 | def __init__(self): 9 | self.coords = [(10, 10), (200, 10), (300, 200), (400, 100)] 10 | self.hit = None 11 | def draw(self, dc): 12 | coords = self.coords 13 | dc.setrgba(0, 0, 0.5, 1) 14 | dc.moveto(coords[0][0], coords[0][1]) 15 | dc.curveto(coords[1][0], coords[1][1], coords[2][0], coords[2][1], 16 | coords[3][0], coords[3][1]) 17 | dc.stroke() 18 | dc.setrgba(0, 0.5, 0, 0.5) 19 | dc.moveto(coords[0][0], coords[0][1]) 20 | dc.lineto(coords[1][0], coords[1][1]) 21 | dc.stroke() 22 | dc.moveto(coords[2][0], coords[2][1]) 23 | dc.lineto(coords[3][0], coords[3][1]) 24 | dc.stroke() 25 | dc.setrgba(1, 0, 0, .5) 26 | for x, y in coords: 27 | dc.rectangle(x - 3, y - 3, 6, 6) 28 | dc.fill() 29 | def mouse(self, button, mods, x, y): 30 | if button == 1: 31 | for i in range(4): 32 | if hypot(x - self.coords[i][0], y - self.coords[i][1]) < 4: 33 | self.hit = i 34 | elif button == -1: 35 | self.hit = None 36 | elif self.hit != None: 37 | self.coords[self.hit] = (x, y) 38 | self.view.dirty() 39 | 40 | win = x3.window(0, "beztest", my_callback) 41 | 42 | x3.view(win, 259, bez()) 43 | 44 | x3.main() 45 | -------------------------------------------------------------------------------- /x3/pyrex/main.py: -------------------------------------------------------------------------------- 1 | import x3 2 | 3 | def my_callback(cmd, what, arg, more): 4 | print cmd, what, arg 5 | 6 | class my_viewclient: 7 | def key(self, name, mods, code): 8 | print name, mods, code 9 | return 1 10 | def mouse(self, buttons, mods, x, y): 11 | print buttons, mods, x, y 12 | def draw(self, dc): 13 | print 'rect:', dc.rect 14 | dc.moveto(0, 0) 15 | dc.lineto(100, 100) 16 | print dc.currentpoint() 17 | dc.stroke() 18 | dc.selectfont("Nimbus Sans L", 0, 0) 19 | dc.setfontsize(12) 20 | dc.moveto(50, 10) 21 | dc.showtext(u"\u00a1hello, world!") 22 | print dc.textextents(u"\u00a1hello, world!") 23 | 24 | win = x3.window(0, "foo", my_callback) 25 | 26 | m = x3.menu(win, "bar") 27 | 28 | x3.menuitem(m, "baz", "bazz", "b") 29 | x3.menusep(m) 30 | x3.menuitem(m, "Quit", "quit", "q") 31 | 32 | v = x3.vbox(win, 0, 12) 33 | 34 | v.setpacking(True, False, 0) 35 | x3.button(v, "butt", u"\u00a1hello!") 36 | x3.edittext(v, "quux") 37 | v.setpacking(True, True, 0) 38 | x3.view(v, 263, my_viewclient()) 39 | 40 | x3.main() 41 | -------------------------------------------------------------------------------- /x3/pyrex/x3.pyx: -------------------------------------------------------------------------------- 1 | cdef extern from "Python.h": 2 | void *PyMem_Malloc(int size) except NULL 3 | void Py_INCREF(object) 4 | int PyUnicode_Check(object str) 5 | object PyUnicode_AsUTF8String(object str) 6 | 7 | cdef extern from "x3.h": 8 | ctypedef struct x3widget 9 | ctypedef struct x3dc: 10 | int x 11 | int y 12 | int width 13 | int height 14 | cdef enum x3windowflags: 15 | x3window_main = 1 16 | x3window_dialog = 2 17 | ctypedef struct x3viewclient: 18 | void (*destroy)(x3viewclient *self) 19 | void (*mouse)(x3viewclient *self, int buttons, int mods, 20 | double x, double y) 21 | int (*key)(x3viewclient *self, char *keyname, int mods, int key) 22 | void (*draw)(x3viewclient *self, x3dc *dc) 23 | 24 | # client extension 25 | void *py_client 26 | ctypedef struct x3extents: 27 | double x_bearing 28 | double y_bearing 29 | double width 30 | double height 31 | double x_advance 32 | double y_advance 33 | void x3init(int argc, char **argv) 34 | void x3main() 35 | x3widget *x3window(x3windowflags flags, char *name, 36 | int (callback)(x3widget *w, void *data, char *cmd, char *what, char *arg, void *more), 37 | void *data) 38 | x3widget *x3menu(x3widget *parent, char *name) 39 | x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut) 40 | x3widget *x3menusep(x3widget *parent) 41 | x3widget *x3align(x3widget *parent, int alignment) 42 | x3widget *x3pad(x3widget *parent, int t, int b, int l, int r) 43 | x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing) 44 | x3widget *x3hpane(x3widget *parent) 45 | x3widget *x3vpane(x3widget *parent) 46 | x3widget *x3button(x3widget *parent, char *name, char *label) 47 | x3widget *x3label(x3widget *parent, char *text) 48 | x3widget *x3edittext(x3widget *parent, char *cmd) 49 | x3widget *x3view(x3widget *parent, int flags, x3viewclient *vc) 50 | void x3viewclient_init(x3viewclient *vc) 51 | void x3view_dirty(x3widget *w) 52 | void x3view_scrollto(x3widget *w, int x, int y, int width, int height) 53 | 54 | void x3window_setdefaultsize(x3widget *w, int x, int y) 55 | void x3setactive(x3widget *w, int active) 56 | void x3setpacking(x3widget *w, int fill, int expand, int padding) 57 | void x3pane_setsizing(x3widget *w, int c1r, int c1s, int c2r, int c2s) 58 | int x3hasfocus(x3widget *w) 59 | 60 | void x3moveto(x3dc *dc, double x, double y) 61 | void x3lineto(x3dc *dc, double x, double y) 62 | void x3curveto(x3dc *dc, 63 | double x1, double y1, 64 | double x2, double y2, 65 | double x3, double y3) 66 | void x3closepath(x3dc *dc) 67 | void x3rectangle(x3dc *dc, double x, double y, double width, double height) 68 | void x3getcurrentpoint(x3dc *dc, double *px, double *py) 69 | void x3setrgba(x3dc *dc, unsigned int rgba) 70 | void x3setlinewidth(x3dc *dc, double w) 71 | void x3fill(x3dc *dc) 72 | void x3stroke(x3dc *dc) 73 | void x3selectfont(x3dc *dc, char *fontname, int slant, int weight) 74 | void x3setfontsize(x3dc *dc, double size) 75 | void x3showtext(x3dc *dc, char *text) 76 | void x3textextents(x3dc *dc, char *text, x3extents *extents) 77 | X3_GMW(g, m, w) 78 | 79 | cdef object strmaybenull(char *str): 80 | if str == NULL: 81 | return None 82 | else: 83 | return str 84 | 85 | cdef object utf8(object ustr): 86 | if PyUnicode_Check(ustr): 87 | return PyUnicode_AsUTF8String(ustr) 88 | else: 89 | return ustr 90 | 91 | ctypedef struct x3viewclient_py: 92 | x3viewclient base 93 | # client extension 94 | void *py_client 95 | 96 | cdef class widget: 97 | cdef x3widget *w 98 | def hasfocus(self): 99 | return x3hasfocus(self.w) 100 | 101 | cdef int x3py_callback(x3widget *window, void *data, char *cmd, char *what, 102 | char *arg, void *more): 103 | callback = data 104 | callback(cmd, strmaybenull(what), strmaybenull(arg), None) 105 | 106 | cdef class window(widget): 107 | def __new__(self, int flags, char *name, callback): 108 | self.w = x3window(flags, name, x3py_callback, callback) 109 | def setdefaultsize(self, int width, int height): 110 | x3window_setdefaultsize(self.w, width, height) 111 | 112 | cdef class menu(widget): 113 | def __new__(self, widget parent, name): 114 | uname = utf8(name) 115 | self.w = x3menu(parent.w, uname) 116 | 117 | cdef class menuitem(widget): 118 | def __new__(self, widget parent, name, char *cmd, char *shortcut = NULL): 119 | uname = utf8(name) 120 | self.w = x3menuitem(parent.w, uname, cmd, shortcut) 121 | 122 | cdef class menusep(widget): 123 | def __new__(self, widget parent): 124 | self.w = x3menusep(parent.w) 125 | 126 | cdef class align(widget): 127 | def __new__(self, widget parent, int alignment): 128 | self.w = x3align(parent.w, alignment) 129 | 130 | cdef class pad(widget): 131 | def __new__(self, widget parent, int t, int b, int l, int r): 132 | self.w = x3pad(parent.w, t, b, l, r) 133 | 134 | cdef class vbox(widget): 135 | def __new__(self, widget parent, homogeneous, int spacing): 136 | self.w = x3vbox(parent.w, not not homogeneous, spacing) 137 | def setpacking(self, fill, expand, int padding): 138 | x3setpacking(self.w, not not fill, not not expand, padding) 139 | 140 | cdef class hpane(widget): 141 | def __new__(self, widget parent): 142 | self.w = x3hpane(parent.w) 143 | def setsizing(self, c1r, c1s, c2r, c2s): 144 | x3pane_setsizing(self.w, not not c1r, not not c1s, not not c2r, not not c2s) 145 | 146 | cdef class vpane(widget): 147 | def __new__(self, widget parent): 148 | self.w = x3vpane(parent.w) 149 | def setsizing(self, c1r, c1s, c2r, c2s): 150 | x3pane_setsizing(self.w, not not c1r, not not c1s, not not c2r, not not c2s) 151 | 152 | cdef class button(widget): 153 | def __new__(self, widget parent, char *name, label): 154 | ulabel = utf8(label) 155 | self.w = x3button(parent.w, name, ulabel) 156 | 157 | cdef class label(widget): 158 | def __new__(self, widget parent, text): 159 | ulabel = utf8(text) 160 | self.w = x3label(parent.w, ulabel) 161 | 162 | cdef class edittext(widget): 163 | def __new__(self, widget parent, char *cmd): 164 | self.w = x3edittext(parent.w, cmd) 165 | 166 | cdef int x3py_key(x3viewclient *self, char *keyname, int mods, int key): 167 | py_client = (self).py_client 168 | return py_client.key(keyname, mods, key) 169 | 170 | cdef void x3py_mouse(x3viewclient *self, int button, int mods, double x, double y): 171 | py_client = (self).py_client 172 | py_client.mouse(button, mods, x, y) 173 | 174 | cdef int dbl_to_byte(double x): 175 | if x <= 0: return 0 176 | if x >= 1: return 255 177 | return x * 255 + 0.5 178 | 179 | cdef class x3dc_wrap: 180 | cdef x3dc *dc 181 | 182 | property rect: 183 | def __get__(self): 184 | return (self.dc.x, self.dc.y, self.dc.width, self.dc.height) 185 | 186 | def moveto(self, double x, double y): 187 | x3moveto(self.dc, x, y) 188 | def lineto(self, double x, double y): 189 | x3lineto(self.dc, x, y) 190 | def curveto(self, double x1, double y1, double x2, double y2, double x3, double y3): 191 | x3curveto(self.dc, x1, y1, x2, y2, x3, y3) 192 | def closepath(self): 193 | x3closepath(self.dc) 194 | def rectangle(self, double x, double y, double width, double height): 195 | x3rectangle(self.dc, x, y, width, height) 196 | def currentpoint(self): 197 | cdef double x 198 | cdef double y 199 | x3getcurrentpoint(self.dc, &x, &y) 200 | return (x, y) 201 | def setrgba(self, double r, double g, double b, double a): 202 | x3setrgba(self.dc, (dbl_to_byte(r) << 24) | 203 | (dbl_to_byte(g) << 16) | 204 | (dbl_to_byte(b) << 8) | 205 | dbl_to_byte(a)) 206 | def setlinewidth(self, double w): 207 | x3setlinewidth(self.dc, w) 208 | def fill(self): 209 | x3fill(self.dc) 210 | def stroke(self): 211 | x3stroke(self.dc) 212 | def selectfont(self, char *name, int slant, int weight): 213 | x3selectfont(self.dc, name, slant, weight) 214 | def setfontsize(self, double size): 215 | x3setfontsize(self.dc, size) 216 | def showtext(self, text): 217 | utext = utf8(text) 218 | x3showtext(self.dc, utext) 219 | def textextents(self, text): 220 | cdef x3extents ext 221 | utext = utf8(text) 222 | x3textextents(self.dc, utext, &ext) 223 | return (ext.x_bearing, ext.y_bearing, 224 | ext.width, ext.height, 225 | ext.x_advance, ext.y_advance) 226 | 227 | 228 | cdef void x3py_draw(x3viewclient *self, x3dc *dc): 229 | cdef x3dc_wrap dc_wrap 230 | py_client = (self).py_client 231 | dc_wrap = x3dc_wrap() 232 | dc_wrap.dc = dc 233 | py_client.draw(dc_wrap) 234 | 235 | cdef class view(widget): 236 | def __new__(self, widget parent, int flags, viewclient): 237 | cdef x3viewclient_py *vc 238 | vc = PyMem_Malloc(sizeof(x3viewclient_py)) 239 | x3viewclient_init(&vc.base) 240 | vc.py_client = viewclient 241 | Py_INCREF(viewclient) 242 | vc.base.key = x3py_key 243 | vc.base.mouse = x3py_mouse 244 | vc.base.draw = x3py_draw 245 | self.w = x3view(parent.w, flags, &vc.base) 246 | viewclient.view = self 247 | def dirty(self): 248 | x3view_dirty(self.w) 249 | def scrollto(self, int x, int y, int width, int height): 250 | x3view_scrollto(self.w, x, y, width, height) 251 | 252 | def main(): 253 | x3main() 254 | 255 | def gmw(g, m, w): 256 | return X3_GMW(g, m, w) 257 | platform = gmw("gtk", "mac", "win32") 258 | 259 | x3init(0, NULL) 260 | -------------------------------------------------------------------------------- /x3/test.c: -------------------------------------------------------------------------------- 1 | /* A test app for X3. */ 2 | 3 | #include "x3.h" 4 | #include 5 | 6 | int 7 | mycallback(x3widget *w, void *data, 8 | char *cmd, char *what, char *arg, void *more) 9 | { 10 | printf("my callback: cmd=\"%s\", what=\"%s\", arg=\"%s\"\n", 11 | cmd, what ? what : "(null)", arg ? arg : "(null)"); 12 | return 1; 13 | } 14 | 15 | #if defined(X3_GTK) || defined(X3_CARBON) 16 | static void test_viewclient_draw(x3viewclient *self, x3dc *dc) 17 | { 18 | if (dc->buf) { 19 | int i, j; 20 | 21 | for (i = 0; i < dc->height; i++) { 22 | for (j = 0; j < dc->width; j++) { 23 | dc->buf[i * dc->rowstride + j * 3] = i; 24 | dc->buf[i * dc->rowstride + j * 3 + 1] = 0x80; 25 | dc->buf[i * dc->rowstride + j * 3 + 2] = j; 26 | } 27 | } 28 | } else { 29 | x3extents ext; 30 | 31 | x3moveto(dc, 10, 10); 32 | x3curveto(dc, 200, 10, 100, 100, 490, 100); 33 | x3stroke(dc); 34 | x3selectfont(dc, "Nimbus Roman No9 L", 0, 0); 35 | x3moveto(dc, 10, 50); 36 | x3setfontsize(dc, 16); 37 | x3showtext(dc, "hello, world!"); 38 | x3textextents(dc, "hello, world!", &ext); 39 | printf("text extents: %g %g %g %g %g %g\n", 40 | ext.x_bearing, ext.y_bearing, 41 | ext.width, ext.height, 42 | ext.x_advance, ext.y_advance); 43 | } 44 | } 45 | 46 | static int test_viewclient_key(x3viewclient *self, 47 | char *keyname, int mods, int key) 48 | { 49 | printf("view key: %s %d %d\n", keyname, mods, key); 50 | return 1; 51 | } 52 | 53 | static void test_viewclient_mouse(x3viewclient *self, 54 | int button, int mods, 55 | double x, double y) 56 | { 57 | printf("view button: %d %d %g %g\n", button, mods, x, y); 58 | } 59 | 60 | x3viewclient *test_viewclient(void) 61 | { 62 | x3viewclient *result = (x3viewclient *)malloc(sizeof(x3viewclient)); 63 | x3viewclient_init(result); 64 | result->draw = test_viewclient_draw; 65 | result->key = test_viewclient_key; 66 | result->mouse = test_viewclient_mouse; 67 | 68 | return result; 69 | } 70 | 71 | #ifdef X3_USEMAIN 72 | int 73 | main(int argc, char **argv) 74 | { 75 | x3widget *mainwin; 76 | x3widget *pane; 77 | x3widget *vbox; 78 | x3widget *m; 79 | x3viewflags viewflags = x3view_click | x3view_hover | x3view_key; 80 | 81 | x3init(&argc, &argv); 82 | mainwin = x3window(x3window_main, "untitled", mycallback, NULL); 83 | #if 1 84 | m = x3menu(mainwin, "F\xc2\xa1le"); 85 | x3menuitem(m, "Save", "save", "s"); 86 | x3setactive(x3menuitem(m, "Save As...", "sava", "S"), 0); 87 | x3menusep(m); 88 | x3menuitem(m, "Preferences...", "pref", ","); 89 | 90 | m = x3menu(mainwin, "Edit"); 91 | 92 | x3menuitem(m, "ctrl-delete", "cdel", "Delete"); 93 | x3menuitem(m, "ctrl-f1", "cf1", "F1"); 94 | if (0) { 95 | int i, j; 96 | 97 | for (j = 0; j < 3; j++) { 98 | char mname[16]; 99 | mname[0] = '0' + j; 100 | mname[1] = 0; 101 | m = x3menu(mainwin, mname); 102 | for (i = 0x20 + 0x20 * j; i < 0x40 + 0x20 * j; i++) { 103 | char name[16]; 104 | 105 | name[0] = i; 106 | name[1] = 0; 107 | x3menuitem(m, name, name, name); 108 | } 109 | } 110 | } 111 | #endif 112 | 113 | #if 1 114 | pane = x3vpane(mainwin); 115 | vbox = x3vbox(x3pad(pane, 10, 10, 10, 10), 0, 5); 116 | 117 | x3setpacking(vbox, FALSE, FALSE, 0); 118 | x3button(x3align(vbox, x3center), "foo", "Click me!"); 119 | x3button(vbox, "bar", "Click me too!"); 120 | x3setactive(x3button(vbox, "bar", "But not me"), 0); 121 | x3label(vbox, "I am a label."); 122 | x3edittext(vbox, "etxt"); 123 | #endif 124 | 125 | x3setpacking(vbox, TRUE, TRUE, 0); 126 | viewflags |= x3view_2d; 127 | viewflags |= x3view_scroll; 128 | x3view(pane, viewflags, test_viewclient()); 129 | 130 | #if 0 131 | x3_window_show(mainwin); 132 | #endif 133 | 134 | x3main(); 135 | return 0; 136 | } 137 | #endif 138 | #endif 139 | 140 | #ifdef X3_USEWINMAIN 141 | int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, 142 | int iCmdShow) 143 | { 144 | //printf("winmain\n"); 145 | x3widget *mainwin; 146 | x3widget *vbox; 147 | 148 | x3init_win32(hInstance); 149 | 150 | mainwin = x3window(x3window_main, "untitled", mycallback, NULL); 151 | x3_window_show(mainwin); 152 | vbox = x3vbox(mainwin, 0, 5); 153 | x3button(vbox, "foo", "Click me!"); 154 | x3button(vbox, "bar", "And me too!"); 155 | x3main(); 156 | return 0; 157 | } 158 | #endif 159 | -------------------------------------------------------------------------------- /x3/x3.h: -------------------------------------------------------------------------------- 1 | /* X3 is a lightweight toolset for building cross-platform applications. */ 2 | 3 | typedef struct _x3widget x3widget; 4 | 5 | typedef struct _x3viewclient x3viewclient; 6 | 7 | typedef enum { 8 | x3window_main = 1, 9 | x3window_dialog = 2, 10 | } x3windowflags; 11 | 12 | typedef enum { 13 | x3center = 0, 14 | x3left = 1, 15 | x3right = 2, 16 | x3hfill = 3, 17 | x3top = 4, 18 | x3bottom = 8, 19 | x3vfill = 12, 20 | 21 | x3topleft = 5, 22 | x3topright = 6, 23 | x3bottomleft = 9, 24 | x3bottomright = 10, 25 | } x3alignment; 26 | 27 | typedef enum { 28 | x3view_click = 1, 29 | x3view_hover = 2, 30 | x3view_key = 4, 31 | x3view_2d = 0x100, 32 | x3view_rgb = 0x200, 33 | x3view_scroll = 0x10000 34 | } x3viewflags; 35 | 36 | typedef struct _x3dc x3dc; 37 | 38 | struct _x3viewclient { 39 | void (*destroy)(x3viewclient *self); 40 | void (*mouse)(x3viewclient *self, int buttons, int mods, 41 | double x, double y); 42 | int (*key)(x3viewclient *self, char *keyname, int mods, int key); 43 | void (*draw)(x3viewclient *self, x3dc *dc); 44 | }; 45 | 46 | /* Windows and Carbon both use left/top/right/bottom nomenclature, 47 | at least for int rects, while gtk+ uses x/y/width/height. */ 48 | typedef struct { 49 | double x0; 50 | double y0; 51 | double x1; 52 | double y1; 53 | } x3rect; 54 | 55 | #ifdef X3_GTK 56 | 57 | #include 58 | 59 | #define X3_GMW(g, m, w) g 60 | 61 | struct _x3dc { 62 | /* move to x3rect structure? */ 63 | int x; 64 | int y; 65 | int width; 66 | int height; 67 | 68 | cairo_t *cr; 69 | 70 | /* for rgb drawing */ 71 | unsigned char *buf; 72 | int rowstride; 73 | }; 74 | 75 | struct _x3widget { 76 | char *name; 77 | x3widget *parent; 78 | GtkWidget *widget; 79 | }; 80 | 81 | typedef cairo_text_extents_t x3extents; 82 | 83 | #define X3_SHIFT_MASK GDK_SHIFT_MASK 84 | #define X3_CONTROL_MASK GDK_CONTROL_MASK 85 | #define X3_ALT_MASK GDK_MOD1_MASK 86 | /* there is no X3_CMD_MASK in gtk */ 87 | 88 | #define X3_USEMAIN 89 | 90 | #endif 91 | 92 | #ifdef X3_CARBON 93 | 94 | #define X3_GMW(g, m, w) m 95 | 96 | #include 97 | 98 | struct _x3dc { 99 | /* move to x3rect structure? */ 100 | int x; 101 | int y; 102 | int width; 103 | int height; 104 | 105 | CGContextRef ctx; 106 | CGMutablePathRef path; 107 | 108 | /* for rgb drawing */ 109 | char *buf; 110 | int rowstride; 111 | }; 112 | 113 | typedef struct _x3type x3type; 114 | 115 | struct _x3type { 116 | void (*sizereq)(x3widget *w); 117 | void (*sizealloc)(x3widget *w, x3rect *r); 118 | void (*add)(x3widget *w, x3widget *child); 119 | }; 120 | 121 | typedef enum { 122 | x3carbonnone, 123 | x3carbonhiview, 124 | x3carbonwindow, 125 | x3carbonmenu, 126 | x3carbonmenuitem, 127 | } x3carbonvar; 128 | 129 | typedef enum { 130 | x3flag_needsizereq = 1, 131 | x3flag_needsizealloc = 2 132 | } x3widgetflags; 133 | 134 | struct _x3widget { 135 | const x3type *type; 136 | x3widgetflags flags; 137 | x3rect sizerequest; 138 | char *name; 139 | x3widget *parent; 140 | x3carbonvar var; 141 | union { 142 | HIViewRef hiview; 143 | WindowRef window; 144 | MenuRef menu; 145 | int menuitem; 146 | } u; 147 | int n_children; 148 | x3widget **children; 149 | }; 150 | 151 | typedef struct { 152 | double x_bearing; 153 | double y_bearing; 154 | double width; 155 | double height; 156 | double x_advance; 157 | double y_advance; 158 | } x3extents; 159 | 160 | #define X3_SHIFT_MASK shiftKey 161 | #define X3_CONTROL_MASK controlKey 162 | #define X3_CMD_MASK cmdKey 163 | #define X3_ALT_MASK optionKey 164 | 165 | /* todo: figure out how to plumb mouse event */ 166 | #define X3_BUTTON1_MASK 0 167 | #define X3_2BUTTON_PRESS 0 168 | #define X3_3BUTTON_PRESS 0 169 | 170 | #define X3_USEMAIN 171 | 172 | #endif 173 | 174 | #ifdef X3_WIN32 175 | 176 | #include 177 | 178 | #define X3_GMW(g, m, w) w 179 | 180 | typedef struct _x3type x3type; 181 | 182 | struct _x3type { 183 | void (*sizereq)(x3widget *w); 184 | void (*sizealloc)(x3widget *w, x3rect *r); 185 | void (*add)(x3widget *w, x3widget *child); 186 | }; 187 | 188 | typedef enum { 189 | x3winnone, 190 | x3winhwnd, 191 | } x3winvar; 192 | 193 | typedef enum { 194 | x3flag_needsizereq = 1, 195 | x3flag_needsizealloc = 2 196 | } x3widgetflags; 197 | 198 | typedef struct { 199 | double x_bearing; 200 | double y_bearing; 201 | double width; 202 | double height; 203 | double x_advance; 204 | double y_advance; 205 | } x3extents; 206 | 207 | struct _x3widget { 208 | const x3type *type; 209 | char *name; 210 | x3widget *parent; 211 | x3widgetflags flags; 212 | x3rect sizerequest; 213 | x3winvar var; // should this be in the type? 214 | union { 215 | HWND hwnd; 216 | } u; 217 | int n_children; 218 | x3widget **children; 219 | }; 220 | 221 | #define X3_USEWINMAIN 222 | 223 | void x3init_win32(HINSTANCE hInstance); 224 | 225 | #endif 226 | 227 | typedef int (*x3window_callback)(x3widget *window, void *data, 228 | char *cmd, char *what, char *arg, 229 | void *more); 230 | 231 | /* Main loop */ 232 | void x3init(int *pargc, char ***pargv); 233 | void x3main(void); 234 | 235 | x3widget *x3window(x3windowflags flags, char *label, 236 | x3window_callback callback, void *callback_data); 237 | x3widget *x3menu(x3widget *parent, char *name); 238 | x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut); 239 | x3widget *x3menusep(x3widget *parent); 240 | x3widget *x3align(x3widget *parent, x3alignment alignment); 241 | x3widget *x3pad(x3widget *parent, int t, int b, int l, int r); 242 | x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing); 243 | x3widget *x3hpane(x3widget *parent); 244 | x3widget *x3vpane(x3widget *parent); 245 | x3widget *x3button(x3widget *parent, char *name, char *label); 246 | x3widget *x3label(x3widget *parent, char *label); 247 | x3widget *x3edittext(x3widget *parent, char *cmd); 248 | x3widget *x3view(x3widget *parent, x3viewflags flags, x3viewclient *vc); 249 | void x3view_dirty(x3widget *w); 250 | void x3view_scrollto(x3widget *w, int x, int y, int width, int height); 251 | 252 | void x3viewclient_init(x3viewclient *vc); 253 | 254 | void x3window_setdefaultsize(x3widget *w, int width, int height); 255 | 256 | void x3pane_setsizing(x3widget *w, int child1_resize, int child1_shrink, 257 | int child2_resize, int child2_shrink); 258 | 259 | void x3setactive(x3widget *w, int active); 260 | int x3hasfocus(x3widget *w); 261 | void x3setpacking(x3widget *w, int fill, int expand, int padding); 262 | 263 | extern int x3n_winopen; 264 | 265 | /* 2d drawing functions */ 266 | 267 | void x3moveto(x3dc *dc, double x, double y); 268 | void x3lineto(x3dc *dc, double x, double y); 269 | void x3curveto(x3dc *dc, 270 | double x1, double y1, 271 | double x2, double y2, 272 | double x3, double y3); 273 | void x3closepath(x3dc *dc); 274 | void x3rectangle(x3dc *dc, double x, double y, double width, double height); 275 | void x3getcurrentpoint(x3dc *dc, double *px, double *py); 276 | void x3setrgba(x3dc *dc, unsigned int rgba); 277 | void x3setlinewidth(x3dc *dc, double w); 278 | void x3fill(x3dc *dc); 279 | void x3stroke(x3dc *dc); 280 | 281 | /* This is an overly simple text api, based on cairo's. It may be phased 282 | out in favor of a more capable one. */ 283 | void x3selectfont(x3dc *dc, char *fontname, int slant, int weight); 284 | void x3setfontsize(x3dc *dc, double size); 285 | void x3showtext(x3dc *dc, char *text); 286 | void x3textextents(x3dc *dc, char *text, x3extents *extents); 287 | 288 | #if defined(X3_CARBON) || defined(X3_WIN32) 289 | /* Internals for carbon/win32 */ 290 | void x3widget_init(x3widget *w, const x3type *type); 291 | void x3add_default(x3widget *parent, x3widget *child); 292 | void x3add(x3widget *parent, x3widget *child); 293 | #endif 294 | -------------------------------------------------------------------------------- /x3/x3common.c: -------------------------------------------------------------------------------- 1 | /* Functions common to more than one platform. */ 2 | 3 | #include 4 | 5 | #include "x3.h" 6 | #include "x3common.h" 7 | 8 | int n_x3needshow = 0; 9 | int n_x3needshow_max = 0; 10 | x3widget **x3needshow = NULL; 11 | 12 | #if defined(X3_GTK) || defined(X3_WIN32) 13 | int x3n_winopen = 0; 14 | #endif 15 | 16 | #if defined(X3_CARBON) || defined(X3_WIN32) 17 | 18 | int n_x3needsizereqs = 0; 19 | int n_x3needsizereqs_max = 0; 20 | x3widget **x3needsizereqs = NULL; 21 | 22 | int n_x3needsizeallocs = 0; 23 | int n_x3needsizeallocs_max = 0; 24 | x3widget **x3needsizeallocs = NULL; 25 | 26 | void x3qsizereq(x3widget *w) 27 | { 28 | if (w && !(w->flags & x3flag_needsizereq)) { 29 | x3qsizereq(w->parent); 30 | if (n_x3needsizereqs == n_x3needsizereqs_max) 31 | x3needsizereqs = (x3widget **)realloc(x3needsizereqs, 32 | sizeof(x3widget *) * 33 | (n_x3needsizereqs_max <<= 1)); 34 | x3needsizereqs[n_x3needsizereqs++] = w; 35 | w->flags |= x3flag_needsizereq; 36 | } 37 | } 38 | 39 | void x3add_default(x3widget *parent, x3widget *child) 40 | { 41 | const int n_children_init = 4; 42 | 43 | child->parent = parent; 44 | if (parent->n_children == 0) 45 | parent->children = (x3widget **)malloc(sizeof(x3widget *) * 46 | n_children_init); 47 | else if (parent->n_children >= n_children_init && 48 | !(parent->n_children & (parent->n_children - 1))) 49 | parent->children = (x3widget **)realloc(parent->children, 50 | sizeof(x3widget *) * 51 | (parent->n_children << 1)); 52 | parent->children[parent->n_children++] = child; 53 | } 54 | 55 | void x3add(x3widget *parent, x3widget *child) 56 | { 57 | parent->type->add(parent, child); 58 | } 59 | 60 | /* Widgets common to carbon and win32 platforms */ 61 | 62 | typedef struct { 63 | x3widget base; 64 | int homogeneous; 65 | int spacing; 66 | int cur_child_prop; 67 | int *child_props; /* bit 0=expand, bit 1=fill, bits 2:31=padding */ 68 | } x3widget_box; 69 | 70 | void x3vbox_sizereq(x3widget *w) 71 | { 72 | x3widget_box *wb = (x3widget_box *)w; 73 | int i; 74 | int spacing = wb->spacing; 75 | 76 | w->sizerequest.x0 = 0; 77 | w->sizerequest.y0 = 0; 78 | w->sizerequest.x1 = 0; 79 | w->sizerequest.y1 = 0; 80 | for (i = 0; i < w->n_children; i++) { 81 | x3widget *child = w->children[i]; 82 | int childw = child->sizerequest.x1 - child->sizerequest.x0; 83 | int childh = child->sizerequest.y1 - child->sizerequest.y0; 84 | int padding = wb->child_props[i] >> 2; 85 | 86 | if (i < w->n_children - 1) 87 | childh += spacing; 88 | w->sizerequest.y1 += childh + 2 * padding; 89 | if (childw > w->sizerequest.x1) 90 | w->sizerequest.x1 = childw; 91 | } 92 | } 93 | 94 | void x3vbox_sizealloc(x3widget *w, x3rect *r) 95 | { 96 | x3widget_box *wb = (x3widget_box *)w; 97 | int i; 98 | x3rect child_r = *r; 99 | int spacing = wb->spacing; 100 | int n_extend = 0; 101 | int n_stretch, i_stretch = 0; 102 | int extra; 103 | 104 | /* todo: impl padding & homog, factor hbox/vbox common */ 105 | printf("vbox sizealloc = (%g, %g) - (%g, %g), req was %g x %g\n", 106 | r->x0, r->y0, r->x1, r->y1, 107 | w->sizerequest.x1, w->sizerequest.y1); 108 | extra = r->y1 - r->y0 - w->sizerequest.y1; 109 | for (i = 0; i < w->n_children; i++) 110 | if (wb->child_props[i] & 1) 111 | n_extend++; 112 | n_stretch = n_extend ? n_extend : w->n_children; 113 | printf("extra = %d, n_stretch = %d\n", extra, n_stretch); 114 | for (i = 0; i < w->n_children; i++) { 115 | x3widget *child = w->children[i]; 116 | int childh = child->sizerequest.y1 - child->sizerequest.y0; 117 | int my_extra; 118 | int next_top; 119 | 120 | if (n_extend == 0 || (wb->child_props[i] & 1)) { 121 | my_extra = (extra * (i_stretch + 1)) / n_stretch - 122 | (extra * i_stretch) / n_stretch; 123 | i_stretch++; 124 | } else 125 | my_extra = 0; 126 | next_top = child_r.y0 + childh + spacing + my_extra; 127 | 128 | if (wb->child_props[i] & 2) { 129 | childh += my_extra; 130 | } else { 131 | child_r.y0 += my_extra >> 1; 132 | } 133 | child_r.y1 = child_r.y0 + childh; 134 | 135 | child->type->sizealloc(child, &child_r); 136 | child->flags &= ~x3flag_needsizealloc; 137 | 138 | child_r.y0 = next_top; 139 | } 140 | } 141 | 142 | void x3vbox_add(x3widget *w, x3widget *child) 143 | { 144 | x3widget_box *wb = (x3widget_box *)w; 145 | const int n_children_init = 4; 146 | 147 | if (w->n_children == 0) 148 | wb->child_props = (int *)malloc(sizeof(int) * n_children_init); 149 | else if (w->n_children >= n_children_init && 150 | !(w->n_children & (w->n_children - 1))) 151 | wb->child_props = (int *)realloc(wb->child_props, 152 | sizeof(int) * (w->n_children << 1)); 153 | wb->child_props[w->n_children] = wb->cur_child_prop; 154 | x3add_default(w, child); 155 | } 156 | 157 | x3type x3vboxtype = { x3vbox_sizereq, 158 | x3vbox_sizealloc, 159 | x3vbox_add }; 160 | 161 | x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing) 162 | { 163 | x3widget_box *result = (x3widget_box *)malloc(sizeof(x3widget_box)); 164 | x3widget_init(&result->base, &x3vboxtype); 165 | x3add(parent, &result->base); 166 | result->homogeneous = homogeneous; 167 | result->spacing = spacing; 168 | result->cur_child_prop = 3; 169 | x3qsizereq(&result->base); 170 | return &result->base; 171 | } 172 | 173 | void x3setpacking(x3widget *w, int fill, int expand, int padding) 174 | { 175 | if (w->type == &x3vboxtype) { 176 | x3widget_box *wb = (x3widget_box *)w; 177 | int child_props = padding << 2; 178 | 179 | if (fill) child_props |= 1; 180 | if (expand) child_props |= 2; 181 | wb->cur_child_prop = child_props; 182 | } 183 | } 184 | 185 | typedef struct { 186 | x3widget base; 187 | x3alignment alignment; 188 | } x3widget_align; 189 | 190 | void x3align_sizereq(x3widget *w) 191 | { 192 | w->sizerequest.x0 = 0; 193 | w->sizerequest.y0 = 0; 194 | w->sizerequest.x1 = 0; 195 | w->sizerequest.y1 = 0; 196 | if (w->n_children) { 197 | x3widget *child = w->children[0]; 198 | int childw = child->sizerequest.x1 - child->sizerequest.x0; 199 | int childh = child->sizerequest.y1 - child->sizerequest.y0; 200 | w->sizerequest.x1 = childw; 201 | w->sizerequest.y1 = childh; 202 | } 203 | } 204 | 205 | void x3align_sizealloc(x3widget *w, x3rect *r) 206 | { 207 | x3widget_align *z = (x3widget_align *)w; 208 | x3alignment a = z->alignment; 209 | int xa = a & 3; 210 | int ya = (a >> 2) & 3; 211 | x3rect child_r = *r; 212 | 213 | printf("align sizealloc = (%g, %g) - (%g, %g)\n", 214 | r->x0, r->y0, r->x1, r->y1); 215 | if (w->n_children) { 216 | x3widget *child = w->children[0]; 217 | if (xa < 3) { 218 | int childw = child->sizerequest.x1 - child->sizerequest.x0; 219 | int pad = r->x1 - r->x0 - childw; 220 | child_r.x0 += (pad * (1 + (xa >> 1) - (xa & 1)) + 1) >> 1; 221 | child_r.x1 = child_r.x0 + childw; 222 | } 223 | if (ya < 3) { 224 | int childh = child->sizerequest.y1 - child->sizerequest.y0; 225 | int pad = r->y1 - r->y0 - childh; 226 | child_r.y0 += (pad * (1 + (ya >> 1) - (ya & 1)) + 1) >> 1; 227 | child_r.y1 = child_r.y0 + childh; 228 | } 229 | 230 | child->type->sizealloc(child, &child_r); 231 | child->flags &= ~x3flag_needsizealloc; 232 | } 233 | } 234 | 235 | x3type x3aligntype = { x3align_sizereq, 236 | x3align_sizealloc, 237 | x3add_default }; 238 | 239 | x3widget *x3align(x3widget *parent, x3alignment alignment) 240 | { 241 | x3widget *result = (x3widget *)malloc(sizeof(x3widget_align)); 242 | x3widget_init(result, &x3aligntype); 243 | x3add(parent, result); 244 | x3qsizereq(result); 245 | ((x3widget_align *)result)->alignment = alignment; 246 | return result; 247 | } 248 | 249 | typedef struct { 250 | x3widget base; 251 | int t, b, l, r; 252 | } x3widget_pad; 253 | 254 | void x3pad_sizereq(x3widget *w) 255 | { 256 | x3widget_pad *z = (x3widget_pad *)w; 257 | w->sizerequest.x0 = 0; 258 | w->sizerequest.y0 = 0; 259 | w->sizerequest.x1 = 0; 260 | w->sizerequest.y1 = 0; 261 | if (w->n_children) { 262 | x3widget *child = w->children[0]; 263 | int childw = child->sizerequest.x1 - child->sizerequest.x0; 264 | int childh = child->sizerequest.y1 - child->sizerequest.y0; 265 | w->sizerequest.x1 = childw + z->l + z->r; 266 | w->sizerequest.y1 = childh + z->t + z->b; 267 | } 268 | } 269 | 270 | void x3pad_sizealloc(x3widget *w, x3rect *r) 271 | { 272 | x3widget_pad *z = (x3widget_pad *)w; 273 | x3rect child_r = *r; 274 | 275 | printf("pad sizealloc = (%g, %g) - (%g, %g)\n", 276 | r->x0, r->y0, r->x1, r->y1); 277 | if (w->n_children) { 278 | x3widget *child = w->children[0]; 279 | child_r.x0 += z->l; 280 | child_r.x1 -= z->r; 281 | child_r.y0 += z->t; 282 | child_r.y1 -= z->b; 283 | 284 | child->type->sizealloc(child, &child_r); 285 | child->flags &= ~x3flag_needsizealloc; 286 | } 287 | } 288 | 289 | x3type x3padtype = { x3pad_sizereq, 290 | x3pad_sizealloc, 291 | x3add_default }; 292 | 293 | x3widget *x3pad(x3widget *parent, int t, int b, int l, int r) 294 | { 295 | x3widget *result = (x3widget *)malloc(sizeof(x3widget_pad)); 296 | x3widget_init(result, &x3padtype); 297 | x3add(parent, result); 298 | x3qsizereq(result); 299 | ((x3widget_pad *)result)->t = t; 300 | ((x3widget_pad *)result)->b = b; 301 | ((x3widget_pad *)result)->l = l; 302 | ((x3widget_pad *)result)->r = r; 303 | return result; 304 | } 305 | 306 | #endif 307 | 308 | void x3initqs(void) 309 | { 310 | n_x3needshow = 0; 311 | x3needshow = (x3widget **)malloc(sizeof(x3widget *) * 312 | (n_x3needshow_max = 16)); 313 | 314 | #if defined(X3_CARBON) || defined(X3_WIN32) 315 | n_x3needsizereqs = 0; 316 | x3needsizereqs = (x3widget **)malloc(sizeof(x3widget *) * 317 | (n_x3needsizereqs_max = 16)); 318 | 319 | n_x3needsizeallocs = 0; 320 | x3needsizeallocs = (x3widget **)malloc(sizeof(x3widget *) * 321 | (n_x3needsizeallocs_max = 16)); 322 | #endif 323 | } 324 | 325 | void x3qshow(x3widget *w) 326 | { 327 | if (n_x3needshow == n_x3needshow_max) 328 | x3needshow = (x3widget **)realloc(x3needshow, 329 | sizeof(x3widget *) * 330 | (n_x3needshow_max <<= 1)); 331 | x3needshow[n_x3needshow++] = w; 332 | } 333 | 334 | void x3sync(void) 335 | { 336 | int i; 337 | 338 | #if defined(X3_CARBON) || defined(X3_WIN32) 339 | 340 | for (i = n_x3needsizereqs - 1; i >= 0; i--) { 341 | x3widget *w = x3needsizereqs[i]; 342 | w->type->sizereq(w); 343 | w->flags &= ~x3flag_needsizereq; 344 | w->flags |= x3flag_needsizealloc; 345 | } 346 | for (i = 0; i < n_x3needsizereqs; i++) { 347 | x3widget *w = x3needsizereqs[i]; 348 | if (w->flags & x3flag_needsizealloc) { 349 | w->type->sizealloc(w, NULL); 350 | w->flags &= ~x3flag_needsizealloc; 351 | } 352 | } 353 | n_x3needsizereqs = 0; 354 | #endif 355 | 356 | for (i = 0; i < n_x3needshow; i++) 357 | x3_window_show(x3needshow[i]); 358 | n_x3needshow = 0; 359 | } 360 | -------------------------------------------------------------------------------- /x3/x3common.h: -------------------------------------------------------------------------------- 1 | #if defined(X3_CARBON) || defined(X3_WIN32) 2 | 3 | void x3qsizereq(x3widget *w); 4 | 5 | void x3add_default(x3widget *parent, x3widget *child); 6 | void x3add(x3widget *parent, x3widget *child); 7 | 8 | #endif 9 | 10 | void x3initqs(void); 11 | void x3qshow(x3widget *w); 12 | void x3sync(void); 13 | 14 | 15 | /* provided by impls to common routines */ 16 | void x3_window_show(x3widget *w); 17 | -------------------------------------------------------------------------------- /x3/x3win32.c: -------------------------------------------------------------------------------- 1 | #include "x3.h" 2 | #include "x3common.h" 3 | #include /* for printf only, probably remove in production */ 4 | 5 | HINSTANCE theInstance = NULL; 6 | 7 | void x3init_win32(HINSTANCE hInstance) 8 | { 9 | theInstance = hInstance; 10 | } 11 | 12 | void x3widget_init(x3widget *w, const x3type *type) 13 | { 14 | w->type = type; 15 | w->name = NULL; 16 | w->parent = NULL; 17 | w->var = x3winnone; 18 | w->u.hwnd = NULL; 19 | w->n_children = 0; 20 | w->children = NULL; 21 | } 22 | 23 | LRESULT CALLBACK x3WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) 24 | { 25 | switch (iMsg) { 26 | case WM_DESTROY: 27 | if (--x3n_winopen <= 0) { 28 | PostQuitMessage(0); 29 | return 0; 30 | } 31 | break; 32 | } 33 | return DefWindowProc(hwnd, iMsg, wParam, lParam); 34 | } 35 | 36 | void x3window_sizereq(x3widget *w) 37 | { 38 | } 39 | 40 | void x3window_sizealloc(x3widget *w, x3rect *r) 41 | { 42 | int i; 43 | RECT rect; 44 | x3rect child_r; 45 | 46 | GetClientRect(w->u.hwnd, &rect); 47 | child_r.x0 = 0; 48 | child_r.x1 = rect.right - rect.left; 49 | child_r.y0 = 0; 50 | child_r.y1 = rect.bottom - rect.top; 51 | printf("x3window_sizealloc (%ld, %ld) - (%ld, %ld)\n", 52 | rect.left, rect.top, rect.right, rect.bottom); 53 | for (i = 0; i < w->n_children; i++) { 54 | x3widget *child = w->children[i]; 55 | if (child->type->sizealloc) 56 | child->type->sizealloc(child, &child_r); 57 | child->flags &= ~x3flag_needsizealloc; 58 | } 59 | } 60 | 61 | x3type x3windowtype = { x3window_sizereq, 62 | x3window_sizealloc, 63 | x3add_default }; 64 | 65 | x3widget *x3window(x3windowflags flags, char *label, 66 | x3window_callback callback, void *data) 67 | { 68 | HWND hwnd; 69 | DWORD style = WS_OVERLAPPEDWINDOW; 70 | x3widget *result = (x3widget *)malloc(sizeof(x3widget)); 71 | WNDCLASSEX wndclass; 72 | 73 | wndclass.cbSize = sizeof(wndclass); 74 | wndclass.style = CS_HREDRAW | CS_VREDRAW; 75 | wndclass.lpfnWndProc = x3WndProc; 76 | wndclass.cbClsExtra = 0; 77 | wndclass.cbWndExtra = 0; 78 | wndclass.hInstance = theInstance; 79 | wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); 80 | wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); 81 | wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); 82 | wndclass.lpszMenuName = NULL; 83 | wndclass.lpszClassName = "x3win"; 84 | wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); 85 | 86 | RegisterClassEx(&wndclass); 87 | 88 | hwnd = CreateWindowEx(0, "x3win", "My window", 89 | style, 100, 100, 300, 300, 90 | NULL, NULL, 91 | theInstance, NULL); 92 | x3widget_init(result, &x3windowtype); 93 | result->var = x3winhwnd; 94 | result->u.hwnd = hwnd; 95 | x3_nwinopen++; 96 | //ShowWindow(hwnd, SW_SHOWNORMAL); 97 | return result; 98 | 99 | } 100 | 101 | void x3_window_show(x3widget *w) 102 | { 103 | ShowWindow(w->u.hwnd, SW_SHOW); 104 | } 105 | 106 | static HWND x3hwnd_of(x3widget *w) 107 | { 108 | while (w->parent) w = w->parent; 109 | return w->var == x3winhwnd ? w->u.hwnd : NULL; 110 | } 111 | 112 | static x3widget *x3widget_new_hwnd(x3widget *parent, char *name, 113 | const x3type *type, 114 | HWND hwnd) 115 | { 116 | x3widget *result = (x3widget *)malloc(sizeof(x3widget)); 117 | x3widget_init(result, type); 118 | result->name = name ? strdup(name) : NULL; 119 | result->var = x3winhwnd; 120 | result->u.hwnd = hwnd; 121 | x3add(parent, result); 122 | x3qsizereq(result); 123 | return result; 124 | } 125 | 126 | void x3button_sizereq(x3widget *w) 127 | { 128 | w->sizerequest.x0 = 0; 129 | w->sizerequest.y0 = 0; 130 | w->sizerequest.x1 = 100; 131 | w->sizerequest.y1 = 20; 132 | #ifdef VERBOSE 133 | printf("button sizereq = (%d, %d) - (%d, %d)\n", 134 | w->sizerequest.x0, w->sizerequest.y0, 135 | w->sizerequest.x1, w->sizerequest.y1); 136 | #endif 137 | } 138 | 139 | void x3button_sizealloc(x3widget *w, x3rect *r) 140 | { 141 | printf("button sizealloc = (%g, %g) - (%g, %g)\n", 142 | r->x0, r->y0, r->x1, r->y1); 143 | if (w->var == x3winhwnd) { 144 | SetWindowPos(w->u.hwnd, HWND_TOP, 145 | r->x0, r->y0, r->x1 - r->x0, r->y1 - r->y0, 146 | SWP_NOZORDER); 147 | } 148 | } 149 | 150 | x3type x3buttontype = { x3button_sizereq, 151 | x3button_sizealloc, 152 | x3add_default }; 153 | 154 | x3widget *x3button(x3widget *parent, char *cmd, char *label) 155 | { 156 | HWND hwnd; 157 | 158 | hwnd = CreateWindow("button", label, 159 | WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 160 | 10, 10, 100, 20, x3hwnd_of(parent), NULL, 161 | theInstance, NULL); 162 | return x3widget_new_hwnd(parent, cmd, &x3buttontype, hwnd); 163 | } 164 | 165 | void x3main(void) 166 | { 167 | MSG msg; 168 | 169 | x3sync(); 170 | while (GetMessage(&msg, NULL, 0, 0)) { 171 | TranslateMessage(&msg); 172 | DispatchMessage(&msg); 173 | } 174 | } 175 | --------------------------------------------------------------------------------