.
1 /*
2 Copyright (C) 2012, 2013, 2014 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 enum PointType {
20 NONE,
21 LINE_QUADRATIC, // line with quadratic handle
22 LINE_DOUBLE_CURVE, // line with two quadratic handles
23 LINE_CUBIC, // line with cubic handles
24 CUBIC,
25 DOUBLE_CURVE, // two quadratic points with a hidden point half way between the two line handles
26 QUADRATIC,
27 HIDDEN,
28 FLOATING,
29 END
30 }
31
32 public class EditPoint : GLib.Object {
33
34 public double x;
35 public double y;
36 public PointType type;
37
38 public unowned EditPoint? prev = null;
39 public unowned EditPoint? next = null;
40
41 public static uint NONE = 0;
42 public static uint ACTIVE = 1;
43 public static uint SELECTED = 1 << 1;
44 public static uint DELETED = 1 << 2;
45 public static uint TIE = 1 << 3;
46 public static uint REFLECTIVE = 1 << 4;
47 public static uint INTERSECTION = 1 << 5;
48 public static uint NEW_CORNER = 1 << 6;
49
50 public uint flags = NONE;
51
52 public bool active_point {
53 get {
54 return (flags & ACTIVE) > 0;
55 }
56
57 set {
58 if (value) {
59 flags |= ACTIVE;
60 } else {
61 flags &= uint.MAX ^ ACTIVE;
62 }
63 }
64 }
65
66 public bool selected_point {
67 get {
68 return (flags & SELECTED) > 0;
69 }
70
71 set {
72 if (value) {
73 flags |= SELECTED;
74 } else {
75 flags &= uint.MAX ^ SELECTED;
76 }
77 }
78 }
79
80 public bool deleted {
81 get {
82 return (flags & DELETED) > 0;
83 }
84
85 set {
86 if (value) {
87 flags |= DELETED;
88 } else {
89 flags &= uint.MAX ^ DELETED;
90 }
91 }
92 }
93
94 public bool tie_handles {
95 get {
96 return (flags & TIE) > 0;
97 }
98
99 set {
100 if (value) {
101 flags |= TIE;
102 } else {
103 flags &= uint.MAX ^ TIE;
104 }
105 }
106 }
107
108 public bool reflective_point {
109 get {
110 return (flags & REFLECTIVE) > 0;
111 }
112
113 set {
114 if (value) {
115 flags |= REFLECTIVE;
116 } else {
117 flags &= uint.MAX ^ REFLECTIVE;
118 }
119 }
120 }
121
122 public int selected_handle = 0;
123
124 public EditPointHandle right_handle;
125 public EditPointHandle left_handle;
126
127 /** Set new position for control point without moving handles. */
128 public double independent_x {
129 get {
130 return x;
131 }
132
133 set {
134 double d = value - x;
135 x = value;
136 right_handle.x -= d;
137 left_handle.x -= d;
138 }
139 }
140
141 public double independent_y {
142 get {
143 return y;
144 }
145
146 set {
147 double d = value - y;
148 y = value;
149 right_handle.y -= d;
150 left_handle.y -= d;
151 }
152 }
153
154 public Color? color = null;
155
156 public EditPoint (double nx = 0, double ny = 0, PointType nt = PointType.NONE) {
157 x = nx;
158 y = ny;
159 type = nt;
160 active_point = false;
161
162 set_active (true);
163
164 if (nt == PointType.FLOATING) {
165 active_point = false;
166 }
167
168 right_handle = new EditPointHandle (this, 0, 7);
169 left_handle = new EditPointHandle (this, PI, 7);
170
171 if (unlikely (nx.is_nan () || ny.is_nan ())) {
172 warning (@"Invalid point at ($nx,$ny).");
173 x = 0;
174 y = 0;
175 }
176 }
177
178 public static bool is_valid (double x, double y) {
179 return likely (x.is_finite () && y.is_finite ()
180 && x > -100000 && x < 100000
181 && y > -100000 && y < 100000);
182 }
183
184 public void set_point_type (PointType t) {
185 type = t;
186 }
187
188 public bool equals (EditPoint e) {
189 return e.x == x
190 && e.y == y
191 && get_right_handle ().x == e.get_right_handle ().x
192 && get_right_handle ().y == e.get_right_handle ().y
193 && get_left_handle ().x == e.get_left_handle ().x
194 && get_left_handle ().y == e.get_left_handle ().y;
195 }
196
197 /** Make handles symmetrical. */
198 public void set_reflective_handles (bool symmetrical) {
199 reflective_point = symmetrical;
200 }
201
202 /** Flip handles if next point on path is in the other direction.
203 * Used to recalculate handles after new point is inserted on a path.
204 */
205 public void recalculate_handles (double px, double py) {
206 double dr, dl;
207 EditPointHandle t;
208
209 if (next == null || get_next ().next != null) {
210 return;
211 }
212
213 if (unlikely (reflective_point || tie_handles)) {
214 warning ("Points on lines can't have tied handles.");
215 return;
216 }
217
218 px = get_next ().get_next ().x;
219 py = get_next ().get_next ().y;
220
221 dr = Math.sqrt (Math.pow (px - right_handle.x, 2) + Math.pow (py - right_handle.y, 2));
222 dl = Math.sqrt (Math.pow (px - left_handle.x, 2) + Math.pow (py - left_handle.y, 2));
223
224 // flip handles
225 if (dl < dr) {
226 t = right_handle;
227 right_handle = left_handle;
228 left_handle = t;
229 }
230 }
231
232 /** Set bezier points for linear paths. */
233 public void recalculate_linear_handles () {
234 unowned EditPointHandle h;
235 unowned EditPoint n;
236 double nx, ny;
237
238 return_if_fail (!is_null (right_handle) && !is_null (left_handle));
239
240 if (prev == null && next != null) {
241 // FIXME: prev = get_next ().last ();
242 }
243
244 // left handle
245 if (prev != null) {
246 n = get_prev ();
247 h = get_left_handle ();
248
249 return_if_fail (!is_null (n) && !is_null (h));
250
251 if (h.type == PointType.LINE_CUBIC) {
252 nx = x + ((n.x - x) / 3);
253 ny = y + ((n.y - y) / 3);
254 h.move_to_coordinate (nx, ny);
255 }
256
257 if (h.type == PointType.LINE_DOUBLE_CURVE) {
258 nx = x + ((n.x - x) / 4);
259 ny = y + ((n.y - y) / 4);
260 h.move_to_coordinate (nx, ny);
261 }
262
263 if (h.type == PointType.LINE_QUADRATIC) {
264 nx = x + ((n.x - x) / 2);
265 ny = y + ((n.y - y) / 2);
266 h.move_to_coordinate (nx, ny);
267 }
268
269 // the other side
270 h = n.get_right_handle ();
271 return_if_fail (!is_null (h) && !is_null (h));
272
273 if (h.type == PointType.LINE_DOUBLE_CURVE) {
274 nx = n.x + ((x - n.x) / 4);
275 ny = n.y + ((y - n.y) / 4);
276 h.move_to_coordinate (nx, ny);
277 }
278
279 if (h.type == PointType.LINE_CUBIC) {
280 nx = n.x + ((x - n.x) / 3);
281 ny = n.y + ((y - n.y) / 3);
282 h.move_to_coordinate (nx, ny);
283 }
284
285 if (h.type == PointType.LINE_QUADRATIC) {
286 nx = n.x + ((x - n.x) / 2);
287 ny = n.y + ((y - n.y) / 2);
288 h.move_to_coordinate (nx, ny);
289 }
290 }
291
292 // right handle
293 if (next != null) {
294 n = get_next ();
295 h = get_right_handle ();
296
297 return_if_fail (!is_null (n) && !is_null (h));
298
299 if (h.type == PointType.LINE_CUBIC) {
300 nx = x + ((n.x - x) / 3);
301 ny = y + ((n.y - y) / 3);
302
303 h.move_to_coordinate (nx, ny);
304 }
305
306 if (h.type == PointType.LINE_DOUBLE_CURVE) {
307 nx = x + ((n.x - x) / 4);
308 ny = y + ((n.y - y) / 4);
309
310 h.move_to_coordinate (nx, ny);
311 }
312
313 if (h.type == PointType.LINE_QUADRATIC) {
314 nx = x + ((n.x - x) / 2);
315 ny = y + ((n.y - y) / 2);
316
317 h.move_to_coordinate (nx, ny);
318 }
319
320 h = n.get_left_handle ();
321 return_if_fail (!is_null (h));
322
323 if (h.type == PointType.LINE_CUBIC) {
324 nx = n.x + ((x - n.x) / 3);
325 ny = n.y + ((y - n.y) / 3);
326
327 h.move_to_coordinate (nx, ny);
328 }
329
330 if (h.type == PointType.LINE_DOUBLE_CURVE) {
331 nx = n.x + ((x - n.x) / 4);
332 ny = n.y + ((y - n.y) / 4);
333
334 h.move_to_coordinate (nx, ny);
335 }
336
337 if (h.type == PointType.LINE_QUADRATIC) {
338 nx = n.x + ((x - n.x) / 2);
339 ny = n.y + ((y - n.y) / 2);
340
341 h.move_to_coordinate (nx, ny);
342 }
343 }
344 }
345
346 public bool is_clockwise () {
347 return get_direction () >= 0;
348 }
349
350 public double get_direction () {
351 if (prev == null) {
352 return 0;
353 }
354
355 // FIXME:
356 return (x - get_prev ().x) * (y + get_prev ().y);
357 }
358
359 public void set_tie_handle (bool tie) {
360 tie_handles = tie;
361 }
362
363 public void process_symmetrical_handles () {
364 process_tied_handle ();
365 right_handle.process_symmetrical_handle ();
366 left_handle.process_symmetrical_handle ();
367 }
368
369 public static void convert_from_line_to_curve (EditPointHandle h) {
370 switch (h.type) {
371 case PointType.LINE_QUADRATIC:
372 h.type = PointType.QUADRATIC;
373 break;
374 case PointType.LINE_DOUBLE_CURVE:
375 h.type = PointType.DOUBLE_CURVE;
376 break;
377 case PointType.LINE_CUBIC:
378 h.type = PointType.CUBIC;
379 break;
380 default:
381 break;
382 }
383 }
384
385 /** This can only be performed if the path has been closed. */
386 public void process_tied_handle ()
387 requires (next != null && prev != null) {
388 double a, b, c, length, angle;
389 EditPointHandle eh;
390 EditPointHandle prev_rh, next_lh;
391
392 eh = right_handle;
393
394 a = left_handle.x - right_handle.x;
395 b = left_handle.y - right_handle.y;
396 c = a * a + b * b;
397
398 if (c == 0) {
399 return;
400 }
401
402 length = sqrt (fabs (c));
403
404 if (right_handle.y < left_handle.y) {
405 angle = acos (a / length) + PI;
406 } else {
407 angle = -acos (a / length) + PI;
408 }
409
410 prev_rh = get_prev ().get_right_handle ();
411 next_lh = get_next ().get_left_handle ();
412
413 convert_from_line_to_curve (next_lh);
414 convert_from_line_to_curve (prev_rh);
415 convert_from_line_to_curve (left_handle);
416 convert_from_line_to_curve (right_handle);
417
418 right_handle.angle = angle;
419 left_handle.angle = angle - PI;
420
421 set_tie_handle (true);
422 eh.move_to_coordinate (right_handle.x, right_handle.y);
423 }
424
425 public EditPoint copy () {
426 EditPoint new_point = new EditPoint ();
427
428 new_point.x = x;
429 new_point.y = y;
430
431 new_point.type = type;
432 new_point.flags = flags;
433
434 new_point.right_handle.angle = right_handle.angle;
435 new_point.right_handle.length = right_handle.length;
436 new_point.right_handle.type = right_handle.type;
437
438 new_point.left_handle.angle = left_handle.angle;
439 new_point.left_handle.length = left_handle.length;
440 new_point.left_handle.type = left_handle.type;
441
442 new_point.color = color;
443
444 return new_point;
445 }
446
447 public double get_distance (double x, double y) {
448 return Path.distance (this.x, x, this.y, y);
449 }
450
451 public unowned EditPointHandle get_left_handle () {
452 if (unlikely (is_null (left_handle))) {
453 warning ("EditPoint.left_handle is null");
454 }
455
456 return left_handle;
457 }
458
459 public unowned EditPointHandle get_right_handle () {
460 if (unlikely (is_null (right_handle))) {
461 warning ("EditPoint.right_handle is null");
462 }
463
464 return right_handle;
465 }
466
467 public unowned EditPoint get_prev () {
468 if (unlikely (prev == null)) {
469 warning ("EditPoint.prev is null");
470 }
471
472 return (!) prev;
473 }
474
475 public unowned EditPoint get_next () {
476 if (unlikely (next == null)) {
477 warning ("EditPoint.next is null");
478 }
479
480 return (!) next;
481 }
482
483 public unowned EditPoint get_link_item () {
484 return this;
485 }
486
487 public void set_independet_position (double tx, double ty) {
488 double rx, ry, lx, ly;
489
490 rx = right_handle.x;
491 ry = right_handle.y;
492
493 lx = left_handle.x;
494 ly = left_handle.y;
495
496 set_position (tx, ty);
497
498 left_handle.move_to_coordinate (lx, ly);
499 right_handle.move_to_coordinate (rx, ry);
500 }
501
502 public void set_position (double tx, double ty) {
503 EditPoint p, n;
504
505 x = tx;
506 y = ty;
507
508 if (unlikely (tx.is_nan () || ty.is_nan ())) {
509 warning (@"Invalid point at ($tx,$ty).");
510 x = 0;
511 y = 0;
512 }
513
514 // move connected quadratic handle
515 if (right_handle.type == PointType.QUADRATIC) {
516 if (next != null) {
517 n = get_next ();
518 n.set_tie_handle (false);
519 n.set_reflective_handles (false);
520 n.left_handle.move_to_coordinate_internal (right_handle.x, right_handle.y);
521 }
522 }
523
524 if (left_handle.type == PointType.QUADRATIC) {
525 if (prev != null && !get_prev ().is_selected ()) {
526 p = get_prev ();
527 p.set_tie_handle (false);
528 p.set_reflective_handles (false);
529 p.right_handle.move_to_coordinate (left_handle.x, left_handle.y);
530 }
531 }
532 }
533
534 public static void to_coordinate (ref double x, ref double y) {
535 double xc, yc, xt, yt, ivz;
536 Glyph g = MainWindow.get_current_glyph ();
537
538 ivz = 1 / g.view_zoom;
539
540 xc = g.allocation.width / 2.0;
541 yc = g.allocation.height / 2.0;
542
543 x *= ivz;
544 y *= ivz;
545
546 xt = x - xc + g.view_offset_x;
547 yt = yc - y - g.view_offset_y;
548
549 x = xt;
550 y = yt;
551 }
552
553 public bool is_selected () {
554 return selected_point;
555 }
556
557 public void set_selected (bool s) {
558 selected_point = s;
559 }
560
561 public bool set_active (bool active) {
562 bool update = (this.active_point != active);
563
564 if (update) {
565 this.active_point = active;
566 }
567
568 return update;
569 }
570
571 public void convert_to_line () {
572 left_handle.convert_to_line ();
573 right_handle.convert_to_line ();
574 }
575
576 public void convert_to_curve () {
577 left_handle.convert_to_curve ();
578 right_handle.convert_to_curve ();
579 }
580
581 public string to_string () {
582 StringBuilder s = new StringBuilder ();
583 s.append (@"Position: $x, $y\n");
584 s.append (@"Left handle: angle: $(left_handle.angle) l: $(left_handle.length)\n");
585 s.append (@"Right handle: angle: $(right_handle.angle) l: $(right_handle.length)\n");
586 return s.str;
587 }
588 }
589
590 }
591