.
1 /*
2 Copyright (C) 2012 Johan Mattsson
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 3 of the
7 License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13 */
14
15 using Math;
16
17 namespace BirdFont {
18
19 public class EditPointHandle : GLib.Object {
20
21 public double length;
22 public unowned EditPoint parent;
23 public PointType type;
24 EditPoint? visual_handle = null;
25 static EditPoint none = new EditPoint ();
26 public bool active;
27 public bool selected;
28
29 public double angle;
30
31 public double x {
32 get {
33 double r = px ();
34
35 if (unlikely (r <= -100000)) {
36 print_position ();
37 move_to (0, 0);
38 }
39
40 return r;
41 }
42
43 set {
44 move_to_coordinate_internal (value, py ());
45
46 if (parent.tie_handles) {
47 parent.process_tied_handle ();
48 }
49
50 if (parent.reflective_point) {
51 process_symmetrical_handle ();
52 }
53
54 process_connected_handle ();
55 }
56 }
57
58 public double y {
59 get {
60 double r = py ();
61
62 if (unlikely (r <= -100000)) {
63 print_position ();
64 move_to (0, 0);
65 }
66
67 return r;
68 }
69
70 set {
71 move_to_coordinate_internal (px (), value);
72
73 if (parent.tie_handles) {
74 parent.process_tied_handle ();
75 }
76
77 if (parent.reflective_point) {
78 process_symmetrical_handle ();
79 }
80
81 process_connected_handle ();
82 }
83 }
84
85 public double independent_x {
86 get {
87 double r = px ();
88
89 if (unlikely (r <= -100000)) {
90 print_position ();
91 move_to (0, 0);
92 }
93
94 return r;
95 }
96
97 set {
98 move_to_coordinate_internal (value, py ());
99 }
100 }
101
102 public double independent_y {
103 get {
104 double r = py ();
105
106 if (unlikely (r <= -100000)) {
107 print_position ();
108 move_to (0, 0);
109 }
110
111 return r;
112 }
113
114 set {
115 move_to_coordinate_internal (px (), value);
116 }
117 }
118
119 public EditPointHandle.empty() {
120 this.parent = none;
121 this.angle = 0;
122 this.length = 10;
123 this.type = PointType.NONE;
124 this.active = false;
125 this.selected = false;
126 }
127
128 public EditPointHandle (EditPoint parent, double angle, double length) {
129 this.parent = parent;
130 this.angle = angle;
131 this.length = length;
132 this.type = PointType.LINE_CUBIC;
133 this.active = false;
134 this.selected = false;
135 }
136
137 public EditPointHandle copy () {
138 EditPointHandle n = new EditPointHandle.empty ();
139 n.angle = angle;
140 n.length = length;
141 n.parent = parent;
142 n.type = type;
143 n.active = active;
144 n.selected = selected;
145 return n;
146 }
147
148 public EditPoint get_parent () {
149 return parent;
150 }
151
152 public void convert_to_line () {
153 parent.tie_handles = false;
154 parent.reflective_point = false;
155
156 switch (type) {
157 case PointType.QUADRATIC:
158 type = PointType.LINE_QUADRATIC;
159 break;
160 case PointType.DOUBLE_CURVE:
161 type = PointType.LINE_DOUBLE_CURVE;
162 break;
163 case PointType.CUBIC:
164 type = PointType.LINE_CUBIC;
165 break;
166 default:
167 break;
168 }
169 }
170
171 public bool is_curve () {
172 switch (type) {
173 case PointType.LINE_QUADRATIC:
174 return false;
175 case PointType.LINE_DOUBLE_CURVE:
176 return false;
177 case PointType.LINE_CUBIC:
178 return false;
179 }
180
181 return true;
182 }
183
184 public void convert_to_curve () {
185 switch (type) {
186 case PointType.LINE_QUADRATIC:
187 type = PointType.QUADRATIC;
188 break;
189 case PointType.LINE_DOUBLE_CURVE:
190 type = PointType.DOUBLE_CURVE;
191 break;
192 case PointType.LINE_CUBIC:
193 type = PointType.CUBIC;
194 break;
195 case PointType.QUADRATIC:
196 break;
197 case PointType.DOUBLE_CURVE:
198 break;
199 case PointType.CUBIC:
200 break;
201 default:
202 warning (@"Type $type");
203 break;
204 }
205 }
206
207 public void set_point_type (PointType point_type) {
208 type = point_type;
209 }
210
211 double px () {
212 assert ((EditPoint?) parent != null);
213 return cos (angle) * length + parent.x;
214 }
215
216 double py () {
217 assert ((EditPoint?) parent != null);
218 return sin (angle) * length + parent.y;
219 }
220
221 internal void print_position () {
222 warning (@"\nEdit point handle at position $(px ()),$(py ()) is not valid.\n"
223 + @"Type: $(parent.type), "
224 + @"Angle: $angle, Length: $length.");
225 }
226
227 public EditPoint get_point () {
228 EditPoint p;
229
230 if (visual_handle == null) {
231 visual_handle = new EditPoint (0, 0);
232 }
233
234 p = (!) visual_handle;
235 p.x = x;
236 p.y = y;
237
238 return p;
239 }
240
241 public bool is_left_handle () {
242 return parent.get_left_handle () == this;
243 }
244
245 public void move_to_coordinate_delta (double dx, double dy) {
246 move_to_coordinate_internal (px () + dx, py () + dy);
247 }
248
249 public void move_to_coordinate (double x, double y) {
250 move_to_coordinate_internal (x, y);
251
252 if (parent.tie_handles) {
253 tie_handle ();
254 }
255
256 if (parent.reflective_point) {
257 tie_handle ();
258 process_symmetrical_handle ();
259 }
260
261 process_connected_handle ();
262 }
263
264 public void move_to_coordinate_internal (double x, double y) {
265 double a, b, c;
266
267 a = parent.x - x;
268 b = parent.y - y;
269 c = a * a + b * b;
270
271 if (unlikely(c == 0)) {
272 angle = 0; // FIXME: this should be a different point type without line handles
273 length = 0;
274 return;
275 }
276
277 length = sqrt (fabs (c));
278
279 if (c < 0) length = -length;
280
281 if (y < parent.y) {
282 angle = acos (a / length) + PI;
283 } else {
284 angle = -acos (a / length) + PI;
285 }
286 }
287
288 public void process_connected_handle () {
289 EditPointHandle h;
290
291 if (unlikely (type == PointType.NONE)) {
292 warning ("Invalid type.");
293 }
294
295 if (type == PointType.QUADRATIC) {
296 if (!is_left_handle ()) {
297 if (parent.next != null) {
298 h = parent.get_next ().get_left_handle ();
299 h.parent.set_reflective_handles (false);
300 h.parent.set_tie_handle (false);
301 h.type = PointType.QUADRATIC;
302 h.move_to_coordinate_internal (x, y);
303 }
304 } else {
305 if (parent.prev != null) {
306 h = parent.get_prev ().get_right_handle ();
307 h.parent.set_reflective_handles (false);
308 h.parent.set_tie_handle (false);
309 h.type = PointType.QUADRATIC;
310 h.move_to_coordinate_internal (x, y);
311 }
312 }
313 }
314 }
315
316 public void process_symmetrical_handle () {
317 if (is_left_handle ()) {
318 parent.get_right_handle ().length = length;
319 parent.get_right_handle ().process_connected_handle ();
320 } else {
321 parent.get_left_handle ().length = length;
322 parent.get_left_handle ().process_connected_handle ();
323 }
324
325 process_connected_handle ();
326 }
327
328 public void tie_handle () {
329 if (is_left_handle ()) {
330 parent.get_right_handle ().angle = angle - PI;
331 parent.get_right_handle ().process_connected_handle ();
332 } else {
333 parent.get_left_handle ().angle = angle - PI;
334 parent.get_left_handle ().process_connected_handle ();
335 }
336
337 process_connected_handle ();
338 }
339
340 public void move_delta_coordinate (double dx, double dy) {
341 double px = px () + dx;
342 double py = py () + dy;
343 move_to_coordinate (px, py);
344 }
345
346 public void move_to (double x, double y) {
347 EditPoint.to_coordinate (ref x, ref y);
348 move_to_coordinate (x, y);
349 }
350
351 public bool is_line () {
352 return PenTool.is_line (type);
353 }
354
355 public static double average_angle (double angle1, double angle2) {
356 EditPointHandle handle = new EditPointHandle (new EditPoint (0, 0, PointType.CUBIC), 0, 1);
357 double x = (cos (angle1) + cos (angle2)) / 2;
358 double y = (sin (angle1) + sin (angle2)) / 2;
359 handle.move_to_coordinate (x, y);
360
361 if (fabs (x) < 0.001 && fabs (y) < 0.001) {
362 return (angle1 + PI / 2) % (2 * PI);
363 }
364
365 return handle.angle;
366 }
367
368 }
369
370 }
371